使用虚拟复制构造函数仅移动类型适配 std::any 是安全的吗?

Move only type adapting std::any with dummy copy constructor is safe?

我想用一个只能移动的类型变量来初始化 std::any。我找到了

编译错误案例

在使用链接答案中的 shared_ptr 解决方法之前,我测试了以下代码:

#include <utility>
#include <iostream>
#include <any>

struct move_only {
    move_only() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    move_only(move_only const&) = delete;
    move_only(move_only &&) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    move_only m;
    std::any a(std::move(m)); // error. copy constructor is required
}

https://wandbox.org/permlink/h6HOSdgOnQYg4a6K

上面的代码输出编译错误,因为move_only没有复制构造函数。

为测试添加复制构造函数

我为测试添加了复制构造函数。

#include <utility>
#include <iostream>
#include <any>

struct move_only {
    move_only() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    move_only(move_only const&) {
        // not called
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    move_only(move_only &&) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    move_only m;
    std::any a(std::move(m)); // success but copy constructor is not called
}

https://wandbox.org/permlink/kxEnIslmVnJNRSn6

然后编译成功,如我所料。我得到了有趣的输出。

move_only::move_only()
move_only::move_only(move_only &&)

好像没有调用复制构造函数。这让我很惊讶。

我想出了以下包装方法。

添加虚拟复制构造函数包装器

#include <utility>
#include <iostream>
#include <any>

struct move_only {
    move_only() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    move_only(move_only const&) = delete;
    move_only(move_only &&) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

struct wrapped_move_only : move_only {
    wrapped_move_only(move_only&& m):move_only(std::move(m)) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    wrapped_move_only(wrapped_move_only const&) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        assert(false);
    }
    wrapped_move_only(wrapped_move_only &&) = default;

};

int main() {
    move_only m;
    wrapped_move_only wmo(std::move(m));
    std::any a(std::move(wmo));
}

https://wandbox.org/permlink/EDhq3KPWKP9fCA9v

move_only的复制构造函数被删除。 class wapped_move_only 继承了 move_only 并添加了复制构造函数。

编译成功,得到如下结果

move_only::move_only()
move_only::move_only(move_only &&)
wrapped_move_only::wrapped_move_only(move_only &&)
move_only::move_only(move_only &&)

我似乎使用提供虚拟复制构造函数的包装器用仅移动类型初始化了 std::any。如果目标只是使用仅移动类型初始化 std::any,则使用 shared_ptr 会更有效。这是我的预期行为。

只要我只对std::any进行移动操作,一旦move_only移动到std::any,这段代码安全吗? 如果 std::any 被复制,则由于调用了 wrapped_move_only 的复制构造函数,资产失败。我想知道 move only case 的安全性。

我也不确定为什么 std::any 的目标需要复制构造函数但它没有被调用。

模板化

如果安全的话,我可以使用模板改进这种方法。模板 add_dummy_copy_constructor 是一种适配器。

#include <utility>
#include <iostream>
#include <any>

struct move_only {
    move_only() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    move_only(move_only const&) = delete;
    move_only(move_only &&) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

template <typename T>
struct add_dummy_copy_constructor : T {
    add_dummy_copy_constructor(T&& t):T(std::move(t)) {}
    add_dummy_copy_constructor(add_dummy_copy_constructor const&) {
        assert(false);
    }
    add_dummy_copy_constructor(add_dummy_copy_constructor &&) = default;
};

int main() {
    move_only m;
    std::any a(add_dummy_copy_constructor(std::move(m)));
}

https://wandbox.org/permlink/guEWPIrK9wiJ3BgW

I am also not sure why std::any's target requires copy constructor but it is not called.

std::any 的设计是成为一种具体类型,可以容纳任何 可复制类型。当你复制 std::any 时,你复制了它下面的任何内容

std::any 需要知道如何 复制底层对象,而不管它是否真的要被复制(它怎么知道这发生?)。因此,如果基础类型不可复制构造,则它一定是一个编译错误。

然而,当我们构建 std::any 本身时,我们知道我们正在构建的具体对象。如果那个具体对象恰好是一个右值,那么我们就可以从构造函数参数移动构造 std::any 的底层对象,而不是复制构造。这是一场免费的胜利。

None 您的代码实际上复制了一个 std::any,因此它的 none 将调用 std::any 的复制构造函数,后者将调用基础类型的复制构造函数。


类似的事情发生在 std::function 上,也许这里的区别会更明显。当我构造一个 std::function<void()> 时,有一个静态要求,即该对象可以在没有参数的情况下调用。如果它不是 invokabe,则为编译错误。

但是简单地构建 std::function<void()> 不会实际调用底层函数 - 这些是单独的操作。您不会期望此断言会触发:

std::function<void()> f = []{ assert(false); }