是否惯用(例如 TBB 的 thread_enumerable_specific')在原始对象上移动赋值调用析构函数
Does idiomatic (e.g. TBB's thread_enumerable_specific') move assignment call destructor on original object
假设我正在使用一个惯用的 Cpp 库(例如英特尔的 TBB)并且在某些 class(例如 TsCountersType _ts_counters;
)中有一个 in-place
成员。这样的成员由它的默认构造函数自动初始化(如果它存在,否则编译错误)除非我自己的 KMeans
的构造函数通过直接调用它的构造函数来明确地初始化它。
然后,如果我在普通的非构造函数方法(例如 init
)中为成员字段分配一个新值(通过调用构造函数创建),那么假设什么是安全的?
- 我知道在那种情况下,赋值的右侧是
rvalue
,因此将调用 move assignment
运算符。
- 我认为可以安全地假设行为良好的惯用 Cpp 代码应该在将新对象移动到内存位置之前调用原始对象(例如
_ts_counters
)的析构函数。但这样的假设合理吗?
默认移动赋值运算符呢?它会在移动之前调用原始对象的析构函数吗?这甚至是相关问题,还是仅在(除其他条件外)未定义显式析构函数的情况下由编译器创建的默认移动赋值运算符?
- 如果是这种情况,如果我有一个类似于 TBB 的场景,而不是
TsCountersType
我有一个简单的自定义 tipe,只有一个 unique_ptr
并且默认所有其他内容(构造函数,析构函数) ,移动作业,...)。 unique_ptr
什么时候会超出范围?
特别是在 TBB 的情况下,我可以假设它发生在他们 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.
示例代码:
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)));
}
};
考虑这段代码:
#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();
}
这产生的输出(经过 clang 和 gcc 测试):
S::S()
S::S()
S &S::operator=(S &&)
S::~S()
S::~S()
那么,到底发生了什么:
W
的构造函数被调用,默认初始化S
- 一个临时
S
是默认构建的,并立即从 W::s
成员 中移出
- 临时对象现在被破坏(就在 init 退出之前)
W::s
在主出口处被破坏。
假设我正在使用一个惯用的 Cpp 库(例如英特尔的 TBB)并且在某些 class(例如 TsCountersType _ts_counters;
)中有一个 in-place
成员。这样的成员由它的默认构造函数自动初始化(如果它存在,否则编译错误)除非我自己的 KMeans
的构造函数通过直接调用它的构造函数来明确地初始化它。
然后,如果我在普通的非构造函数方法(例如 init
)中为成员字段分配一个新值(通过调用构造函数创建),那么假设什么是安全的?
- 我知道在那种情况下,赋值的右侧是
rvalue
,因此将调用move assignment
运算符。 - 我认为可以安全地假设行为良好的惯用 Cpp 代码应该在将新对象移动到内存位置之前调用原始对象(例如
_ts_counters
)的析构函数。但这样的假设合理吗? 默认移动赋值运算符呢?它会在移动之前调用原始对象的析构函数吗?这甚至是相关问题,还是仅在(除其他条件外)未定义显式析构函数的情况下由编译器创建的默认移动赋值运算符?
- 如果是这种情况,如果我有一个类似于 TBB 的场景,而不是
TsCountersType
我有一个简单的自定义 tipe,只有一个unique_ptr
并且默认所有其他内容(构造函数,析构函数) ,移动作业,...)。unique_ptr
什么时候会超出范围?
- 如果是这种情况,如果我有一个类似于 TBB 的场景,而不是
特别是在 TBB 的情况下,我可以假设它发生在他们 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.
示例代码:
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)));
}
};
考虑这段代码:
#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();
}
这产生的输出(经过 clang 和 gcc 测试):
S::S()
S::S()
S &S::operator=(S &&)
S::~S()
S::~S()
那么,到底发生了什么:
W
的构造函数被调用,默认初始化S
- 一个临时
S
是默认构建的,并立即从W::s
成员 中移出
- 临时对象现在被破坏(就在 init 退出之前)
W::s
在主出口处被破坏。