仅移动类型自适应std :: any和伪副本构造函数是安全的吗?

Tak*_*ndo 7 c++ move c++17 stdany

我想std::any使用仅移动类型变量进行初始化。我发现无法移动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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

https://wandbox.org/permlink/kxEnIslmVnJNRSn6

然后编译成功完成。我得到了有趣的输出。

move_only::move_only()
move_only::move_only(move_only &&)
Run Code Online (Sandbox Code Playgroud)

似乎没有调用复制构造函数。我感到惊讶。

我想出了以下包装方法。

添加虚拟副本构造函数包装器

#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));
}
Run Code Online (Sandbox Code Playgroud)

https://wandbox.org/permlink/EDhq3KPWKP9fCA9v

move_only的副本构造函数将被删除。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 &&)
Run Code Online (Sandbox Code Playgroud)

似乎我使用提供伪拷贝构造函数的包装器以仅移动类型初始化了std :: any。如果目标只是使用仅移动类型初始化std :: any,则使用shared_ptr效率更高。这是我的预期行为。

只要我仅将操作std::any一次move_only移动到std::any,此代码安全吗?如果std::any被复制,则资产失败,因为调用了wrapd_move_only的复制构造函数。我想知道仅移动案件的安全性。

我也不确定为什么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)));
}
Run Code Online (Sandbox Code Playgroud)

https://wandbox.org/permlink/guEWPIrK9wiJ3BgW

Bar*_*rry 4

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

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

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

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

您的代码实际上都没有复制 a std::any,因此它们都不会调用 的std::any复制构造函数,而该复制构造函数将调用基础类型的复制构造函数。


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

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

std::function<void()> f = []{ assert(false); }
Run Code Online (Sandbox Code Playgroud)