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

Posted

技术标签:

【中文标题】调用复制 ctor 而不是移动 ctor - 编译器可以发出警告吗?【英文标题】:Copy ctor is called instead of move ctor - Can compiler give a warning? 【发布时间】:2018-02-20 12:27:56 【问题描述】:

在以下代码中,我想移动构造一个没有可用移动构造函数的对象:

class SomeClass

public:
    SomeClass() = default;

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

;

SomeClass& getObject()

    return some_obj;

;

//...

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

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

另一方面,如果它有一个移动构造函数但 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());

【问题讨论】:

std::move 在“它移动某物”的意义上无论如何都没有效果,它只是一个演员表。当然,这个名称具有误导性。 @BaummitAugen 我的意思是,调用者显然希望移动发生,但它不会发生。很高兴看到警告。 您可能可以创建自己的adyady::move,它是一个模板转换,它还测试类型的移动能力,如果不可用,则会引发编译器错误。我不熟悉模板元编程来将其拼凑起来。 如果你总是想移动,你可以删除复制构造函数。 :-) 无论如何,正如鲍姆所说,std::move 没有任何动静;它只是说允许移动源对象,但不是必需的。结果取决于目标将做什么,而不是源。 【参考方案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,了解有关生成警告的更多讨论。

【讨论】:

【参考方案2】:

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

SomeClass(const SomeClass&&) = delete;

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

【讨论】:

【参考方案3】:

是否有可能让编译器发出警告/错误,表明 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 Forward/move helpers):

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

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

这就是编译后不给出警告的原因——一切都是完全合法的,你所做的转换与删除的移动构造函数无关,重载决议选择复制构造函数作为最佳匹配。

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

【讨论】:

以上是关于调用复制 ctor 而不是移动 ctor - 编译器可以发出警告吗?的主要内容,如果未能解决你的问题,请参考以下文章

何时调用移动 ctor?

std::any 用于仅移动模板,其中 copy-ctor 内的 static_assert 等于编译错误,但为啥呢?

C++ 中类的默认成员函数的问题(构造函数、析构函数、运算符 =、复制构造函数)(默认 ctor、dtor、复制 ctor)

复制 ctor 和赋值运算符中是不是存在语义稍有不同的问题?

即使从未调用过副本 CTOR 是不是也需要?

在 C++11 中“删除”复制 ctor/assignment