【问题标题】:Why are move contructors implicitly deleted for my class?为什么为我的班级隐式删除了移动构造函数?
【发布时间】:2020-12-04 04:43:05
【问题描述】:

以下代码...

// main.cpp
#include <grpcpp/grpcpp.h>

class ClientContextContainer {
public:
  ClientContextContainer( int i ) : i_(i) {}

private:
  grpc::ClientContext client_context_;
  int i_;
};

class ArrayContainer {
public:
  ArrayContainer() : ccc_{ {42}, {1138} } {}

private:
  ClientContextContainer ccc_[2];
};

int main( int argc, char* argv[] ) {
  ArrayContainer ac;

 return 0;
}

...产生此错误:

root@178258c7c52d:/tmp# /usr/bin/x86_64-linux-gnu-g++ --version && /usr/bin/x86_64-linux-gnu-g++ -c ./main.cpp
x86_64-linux-gnu-g++ (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.cpp: In constructor 'ArrayContainer::ArrayContainer()':
./main.cpp:15:41: error: use of deleted function 'ClientContextContainer::ClientContextContainer(ClientContextContainer&&)'
   ArrayContainer() : ccc_{ {42}, {1138} } {}
                                         ^
./main.cpp:4:7: note: 'ClientContextContainer::ClientContextContainer(ClientContextContainer&&)' is implicitly deleted because the default definition would be ill-formed:
 class ClientContextContainer {
       ^~~~~~~~~~~~~~~~~~~~~~
./main.cpp:4:7: error: 'grpc::ClientContext::ClientContext(const grpc::ClientContext&)' is private within this context
In file included from /usr/local/include/grpcpp/client_context.h:37,
                 from /usr/local/include/grpcpp/grpcpp.h:53,
                 from ./main.cpp:2:
/usr/local/include/grpcpp/impl/codegen/client_context.h:388:3: note: declared private here
   ClientContext(const ClientContext&);
   ^~~~~~~~~~~~~

我不完全理解编译器错误,但通过我的阅读,它暗示问题在于 class ClientContextContainer 的移动构造函数被隐式删除,因为类的成员 grpc::ClientContext 对象具有私有复制构造函数。 (检查client_context.h 表明它的赋值运算符也是私有的)。

很好。但如果是这种情况,如果我将grpc::ClientContext 成员对象替换为我自己的也具有私有复制构造函数(和赋值运算符)的对象,为什么不能重现编译错误? p>

// main.cpp
//#include <grpcpp/grpcpp.h>

class FauxClientContext {
public:
  FauxClientContext() {}

private:
  FauxClientContext( const FauxClientContext& );
  FauxClientContext& operator=( const FauxClientContext& );
};

class ClientContextContainer {
public:
  ClientContextContainer( int i ) : i_(i) {}

private:
  FauxClientContext client_context_;
  //grpc::ClientContext client_context_;
  int i_;
};

class ArrayContainer {
public:
  ArrayContainer() : ccc_{ {42}, {1138} } {}

private:
  ClientContextContainer ccc_[2];
};

int main( int argc, char* argv[] ) {
  ArrayContainer ac;

 return 0;
}
root@178258c7c52d:/tmp# /usr/bin/x86_64-linux-gnu-g++ --version && /usr/bin/x86_64-linux-gnu-g++ -c ./main.cpp
x86_64-linux-gnu-g++ (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

root@178258c7c52d:/tmp#

编译main.cpp 的第一个版本的错误的真正性质是什么? 我可以成功编译main.cpp 的第二个版本意味着这不是我将编译器错误消息解释为意思。


对于那些熟悉grpc 的人来说,这个问题的根源是:如果对象有一个成员grpc::ClientContext 对象,是否可以在初始化列表中构造一个对象数组?

【问题讨论】:

  • 在过去,在 C++11 之前,阻止复制构造的更好方法之一是创建复制构造函数private,而不是实现它。看起来grpc::ClientContext 被设计为不可复制。
  • 现在我想问题是“为什么一个触发复制构造函数而另一个不触发?”不知道。如果我这样做了,我会回答。
  • 您可能会发现将ClientContextContainer(ClientContextContainer&amp;&amp;) = delete; 添加到ClientContextContainer 的定义中会很有启发性和/或神秘性。也就是说,与其尝试隐式删除移动构造函数,不如显式删除它。 剧透:它仍然可以编译。
  • @JaMit 对于 g++(使用 -std=c++17 -O2 -Wall -pedanti),一旦您向 FauxClientContext 引入一个重要的成员(就像 clientContext 类一样),它就不会编译grpc) 但它至少会与 MSBuild C++17 不一致。
  • 这是一个关于 grpc 还是关于移动构造函数的问题?

标签: c++ c++11 grpc copy-constructor move-constructor


【解决方案1】:

我的原始帖子包含太多的猜测和对您的问题的进一步分析,不幸的是,我的答案的第一次更新包含对实际潜在问题的错误假设。所以这个答案应该是最终版本,并希望在这里解决所有相关问题。

何时隐式删除​​移动构造函数:

摘自https://en.cppreference.com/w/cpp/language/move_constructor

类 T 的隐式声明或默认移动构造函数是 如果以下任何一项为真,则定义为已删除:

  • T 具有无法移动的非静态数据成员(已删除、无法访问或不明确的移动构造函数);
  • T 具有无法移动的直接或虚拟基类(已删除、无法访问或不明确的移动构造函数);
  • T 具有带有已删除或不可访问的析构函数的直接或虚拟基类;
  • T 是一个类似联合的类,并且有一个带有非平凡移动构造函数的变体成员。

被删除的默认移动构造函数被重载忽略 分辨率(否则它会阻止复制初始化 右值)。

为什么会出现编译错误:

根据至少自 C++11 以来的标准,您实际上不应该使用 C++11 直接列表初始化语法(请参阅您的数组)以及为您的案例正确定义的构造函数。至少对于您的情况,这里不需要复制和移动构造函数。 g++ 有一个尚未修复的错误(现在几乎是历史性的)导致您出现问题,请参阅

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63707

至少只要您的类 ClientContextContainer 不包含未隐式(默认)定义的相应类析构函数的成员,您的编译器就符合标准(就我能够测试它而言)。 grpc::ClientContext 不是这种情况,因为它有几个具有非默认析构函数的成员(共享指针)。这就是为什么,即使在所有复制/移动构造函数都是私有/删除的情况下,使用这个简单类的修改后的代码 sn-p 也能很好地工作。尝试在析构函数方面引入一个关键成员,您将能够快速重现编译错误。

旁注:

您在这里的实际问题与保证复制省略无关 - 在 C++17 中引入(实际上这是我的错误假设)。也许它在这里与此 g++ 错误有间接相关性,因为如果该错误是缺少直接初始化但复制初始化的非法底层使用,g++ 也会违反自 C++17 以来标准的保证复制省略要求我想你的情况。

有关详细信息,请参阅

https://en.cppreference.com/w/cpp/language/direct_initialization

https://en.cppreference.com/w/cpp/language/list_initialization

https://en.cppreference.com/w/cpp/language/copy_initialization

https://en.cppreference.com/w/cpp/language/copy_elision

【讨论】:

    【解决方案2】:

    tl;dr:这是一个编译器错误。

    这是 GCC 编译器中的一个错误:

    Brace initialization of array sometimes fails if no copy constructor (gcc.gnu.org/bugzilla)

    截至撰写本文时,该问题已针对 GCC 11.0、10.3 和 9.4 进行了修复,但在当前发布的版本(包括 10.2)中有所体现。

    由于 Jonathan Wakeley 而减少的测试用例:

    struct NonCopyable {
      NonCopyable(const NonCopyable&) = delete;
      NonCopyable(NonCopyable&&) = delete;
      NonCopyable& operator=(const NonCopyable&) = delete;
      NonCopyable& operator=(NonCopyable&&) = delete;
    
      NonCopyable() {}
    
      ~NonCopyable() {} // makes the destructor non-trivial
    };
    
    struct A {
      A(): _a{} {}
      ~A() {}
    
      NonCopyable _a[5];
    } a;
    

    这适用于 clang,但适用于 GCC(参见 GodBolt)。

    【讨论】:

    • 感谢您的更新!只是一个理论:他们赶紧修复这个问题,因为来自 Boost 的 Peter Dimov 也提到了他在那里遇到这个问题...... :)
    • @Secundi:什么?我以为这一切都在我的帐户上:-P
    猜你喜欢
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 2016-03-03
    • 1970-01-01
    • 1970-01-01
    • 2015-04-17
    • 1970-01-01
    • 2018-02-10
    相关资源
    最近更新 更多