是否惯用(例如 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)中为成员字段分配一个新值(通过调用构造函数创建),那么假设什么是安全的?

  1. 我知道在那种情况下,赋值的右侧是 rvalue,因此将调用 move assignment 运算符。
  2. 我认为可以安全地假设行为良好的惯用 Cpp 代码应该在将新对象移动到内存位置之前调用原始对象(例如 _ts_counters)的析构函数。但这样的假设合理吗?
  3. 默认移动赋值运算符呢?它会在移动之前调用原始对象的析构函数吗?这甚至是相关问题,还是仅在(除其他条件外)未定义显式析构函数的情况下由编译器创建的默认移动赋值运算符?

    1. 如果是这种情况,如果我有一个类似于 TBB 的场景,而不是 TsCountersType 我有一个简单的自定义 tipe,只有一个 unique_ptr 并且默认所有其他内容(构造函数,析构函数) ,移动作业,...)。 unique_ptr 什么时候会超出范围?
  4. 特别是在 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 在主出口处被破坏。