调用复制 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&
优于T&&
。如果你真的想禁止这样的参数,你可以添加一个重载的移动构造函数:
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 - 编译器可以发出警告吗?的主要内容,如果未能解决你的问题,请参考以下文章
std::any 用于仅移动模板,其中 copy-ctor 内的 static_assert 等于编译错误,但为啥呢?
C++ 中类的默认成员函数的问题(构造函数、析构函数、运算符 =、复制构造函数)(默认 ctor、dtor、复制 ctor)