let's say I'm working with an idiomatic Cpp library (e.g. Intel's TBB) and have an in-place
member in some class (e.g. TsCountersType _ts_counters;
). Such member is automatically initialized by its default constructor (if it exists, otherwise compile error) unless my own KMeans
's constructor initialize it explicitely by calling its constructor directly.
Then if I assign a new (created by calling constructor) value to the member field in a normal non-constructor method (e.g. init
), what exactly is safe to assume?
rvalue
and thus move assignment
operator will be called. _ts_counters
) destructor before moving the new object to the memory location. But is such assumption sound?What about default move assignment operator? Does it call destructor on the original object before moving in? Is that even relevant question or is default move assignment operator created by the compiler only if (among other conditions) no explicit destructor is defined?
TsCountersType
I have simple custom tipe with a single unique_ptr
and default everything else (constructors, destructors, move assignments, …). When will the unique_ptr
get out of scope then?Specifically in TBB's case, can I assume it happens given their documentation: Supported since C++11. Moves the content of other to *this intact. other is left in an unspecified state, but can be safely destroyed.
Example code:
class KMeans
{
private:
//...
// thread specific counters
typedef std::pair<std::vector<point_t>, std::vector<std::size_t>> TSCounterType;
typedef tbb::enumerable_thread_specific<TSCounterType> TsCountersType;
TsCountersType _ts_counters;
public:
//...
KMeans() : _tbbInit(tbb::task_scheduler_init::automatic) {}
virtual void init(std::size_t points, std::size_t k, std::size_t iters)
{
// When _ts_counters is replaced by a new object the destructor is automatically called on the original object
_ts_counters = TsCountersType(std::make_pair(std::vector<point_t>(k), std::vector<std::size_t>(k)));
}
};
Consider this code:
#include<iostream>
struct S
{
S() { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S(S const &) { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S(S&&) { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S& operator=(S const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this;}
S& operator=(S&&) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this;}
~S() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
struct W
{
W() : s{} {}
void init() { s = S{}; }
private:
S s;
};
int main()
{
W w{};
w.init();
}
The output this produces (clang and gcc tested):
S::S()
S::S()
S &S::operator=(S &&)
S::~S()
S::~S()
So, what happens exactly:
W
's constcructor is called, which default initializes S
S
is default constructed and immediately moved from into W::s
memberW::s
is destructed on main exit.