调用复制ctor而不是移动ctor - 编译器可以发出警告吗?

时间:2018-02-20 12:27:57

标签: c++

在下面的代码中,我想移动构造一个没有move-constructor的对象:

class SomeClass{

public:
    SomeClass() = default;

    SomeClass(const SomeClass&)  = default;
    SomeClass(      SomeClass&&) = delete;

};

SomeClass& getObject(){

    return some_obj;

};

//...

SomeClass obj = std::move( getObject());

编译器给出错误:“使用已删除的函数”。这一切都很好。

另一方面,如果它有一个move-constructor但getObject()返回一个const对象,那么将调用复制构造函数,即使我试图用std :: move移动它。

是否有可能使编译器发出警告/错误,std :: move将无法生效,因为无法移动对象?

class SomeClass{

public:
    SomeClass() = default;

    SomeClass(const SomeClass&)  = default;
    SomeClass(      SomeClass&&) = default;

};

const SomeClass& getObject(){

    return some_obj;

};

//...

SomeClass obj = std::move( getObject());

3 个答案:

答案 0 :(得分:1)

如果您的担心只是确定您获得了最佳性能,那么大多数时候您应该只信任编译器。事实上,您几乎不需要使用std::move()。例如,在上面的示例中,它没有效果。现代编译器可以在移动发生时制定出来。

如果您的类应始终移动且永不复制,则删除其复制构造函数。

但也许你正在编写一个模板函数,如果你把它传递给一个没有移动构造函数的类,或者你还处理其他一些我没有想过的情况,那么它会有很糟糕的表现。在这种情况下,std::is_move_constructible是您可能想要的。试试这个:

#include <type_traits>

#include <boost/serialization/static_warning.hpp>

template<class T>
T &&move_or_warn(T &t)
{
    BOOST_STATIC_WARNING(std::is_move_constructible<T>::value);
    return std::move(t);
}

现在,如果你执行SomeClass obj = std::move_or_warn( getObject());,如果无法移动对象,则应该收到编译器警告。 (虽然我可能会使用普通std::move并单独调用std::is_move_constructible。)

不幸的是,C ++没有(还)有一种标准的方法可以产生你正在寻找的那种程序员指定的警告,这就是为什么我不得不使用boost。有关生成警告的更多讨论,请查看here

答案 1 :(得分:1)

问题来自const rvalue。此类参数与const T&的匹配优于T&&。如果你真的想要禁止这样的参数,你可以添加一个重载的移动构造函数:

SomeClass(const SomeClass&&) = delete;

注意:你正在尝试禁止这些论点,而不是禁止移动行为。因为我们通常无法从const对象“窃取”资源,即使它是rvalue,所以调用复制构造函数而不是移动构造函数是合理的。如果这是XY problem,你应该考虑它是否真的要禁止这些论点。

答案 2 :(得分:1)

  

是否有可能使编译器发出警告/错误,std :: move将无法生效,因为无法移动对象?

std::move不会产生任何影响,这是不正确的。以下代码(在wandbox上试用):

void foo(const SomeClass&) {
    std::cout << "calling foo(const SomeClass&)" << std::endl;
}

void foo(const SomeClass&&) {
    std::cout << "calling foo(const SomeClass&&)" << std::endl;
}

int main() {
    foo(getObject());
    foo(std::move(getObject()));
}

将输出

calling foo(const SomeClass&)
calling foo(const SomeClass&&)

即使你的对象有一个删除的移动构造函数。 原因是std::move本身并没有“移动”任何东西。它只是一个简单的演员(C ++ 17 N4659选秀,23.2.5前锋/移动助手):

template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;

Returns: static_cast<remove_reference_t<T>&&>(t)

这就是为什么编译不会发出警告 - 一切都是完全合法的,你正在做的演员与删除的移动构造函数无关,而重载决策选择复制构造函数作为最佳匹配。

当然,如果你真的需要这样的语义(比如matthewscottgordon的答案中的那个),你可以使用与move不同的语义来定义自己的std::move