Почему конструкторы перемещения неявно удаляются для моего класса?

#c #c 11 #grpc #copy-constructor #move-constructor

#c #c 11 #grpc #copy-constructor #move-конструктор

Вопрос:

Следующий код…

 // 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 amp;amp; /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(ClientContextContaineramp;amp;)'
   ArrayContainer() : ccc_{ {42}, {1138} } {}
                                         ^
./main.cpp:4:7: note: 'ClientContextContainer::ClientContextContainer(ClientContextContaineramp;amp;)' is implicitly deleted because the default definition would be ill-formed:
 class ClientContextContainer {
       ^~~~~~~~~~~~~~~~~~~~~~
./main.cpp:4:7: error: 'grpc::ClientContext::ClientContext(const grpc::ClientContextamp;)' 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 ClientContextamp;);
   ^~~~~~~~~~~~~
 

Я не совсем понимаю ошибку компилятора, но, судя по моему чтению, это означает, что проблема заключается в том, что class ClientContextContainer конструктор перемещения неявно удаляется, потому что объект-член класса grpc::ClientContext имеет конструктор частной копии. (Проверка client_context.h показывает, что его оператор присваивания также является закрытым).

Хорошо. Но если это так, почему я не могу воспроизвести ошибку компиляции, если я заменяю grpc::ClientContext объект-член своим собственным объектом, у которого также есть конструктор частного копирования (и оператор присваивания)?

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

class FauxClientContext {
public:
  FauxClientContext() {}

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

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 amp;amp; /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 ?

Комментарии:

1. В старые времена, до C 11, одним из лучших способов заблокировать создание копирования было создание конструктора копирования private , а не его реализация. Похоже grpc::ClientContext , что он не предназначен для копирования.

2. Теперь, я думаю, возникает вопрос: «Почему один запускает конструктор копирования, а другой нет?» Не знаю. Если бы я это сделал, я бы ответил.

3. Возможно, вам покажется интересным и / или загадочным добавить строку ClientContextContainer(ClientContextContaineramp;amp;) = delete; в ваше определение ClientContextContainer . То есть вместо того, чтобы пытаться удалить конструктор перемещения неявно , сделайте его явно удаленным. Спойлер: он все еще компилируется.

4. @JaMit Для g (с -std= c 17 -O2 -Wall -pedanti) он не будет компилироваться, как только вы введете нетривиальный член в FauxClientContext (как и класс ClientContext grpc), но это произойдет, по крайней мере, при несоответствии MSBuild C 17.

5. Это вопрос о grpc или о конструкторах перемещения в целом?

Ответ №1:

В моем первоначальном сообщении содержалось слишком много предположений и дальнейшего анализа вашей проблемы, первое обновление моего ответа содержало ошибочное предположение о фактических основных проблемах здесь, к сожалению, далее. Итак, этот ответ должен быть окончательной версией и, надеюсь, решает все соответствующие проблемы здесь.

Когда конструкторы перемещения неявно удаляются:

Выдержка из https://en.cppreference.com/w/cpp/language/move_constructor

Неявно объявленный или используемый по умолчанию конструктор перемещения для класса T определяется как удаленный, если верно любое из следующих значений:

  • T имеет нестатические элементы данных, которые нельзя перемещать (имеют удаленные, недоступные или неоднозначные конструкторы перемещения);
  • T имеет прямой или виртуальный базовый класс, который нельзя переместить (имеет удаленные, недоступные или неоднозначные конструкторы перемещения);
  • T имеет прямой или виртуальный базовый класс с удаленным или недоступным деструктором;
  • T — класс, подобный объединению, и имеет элемент variant с нетривиальным конструктором перемещения.

Конструктор перемещения по умолчанию, который удаляется, игнорируется разрешением перегрузки (в противном случае это предотвратило бы инициализацию копирования из rvalue).

Почему вы получаете ошибку компиляции:

Согласно стандарту, по крайней мере, уже с C 11, вы не должны этого делать, поскольку вы используете синтаксис прямой инициализации списка C 11 (см. Ваш Массив) с четко определенным конструктором для вашего случая правильно. Конструкторы копирования и перемещения здесь не требуются, по крайней мере, для вашего случая. в g все еще не исправлена ошибка (почти историческая сейчас), которая вызывает ваши проблемы здесь, см.

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

Ваш компилятор работает стандартно (насколько я смог его протестировать), по крайней мере, до тех пор, пока ваш класс ClientContextContainer не содержит элемент, соответствующий деструктор класса которого неявно (по умолчанию) определен. Это не относится к grpc::ClientContext, поскольку он имеет несколько членов (общих указателей) с деструкторами, отличными от стандартных. Вот почему ваш модифицированный фрагмент кода с этим простым классом хорошо работает даже для случая, когда все конструкторы копирования / перемещения являются закрытыми / удаленными. Попробуйте ввести здесь критический элемент в терминах деструктора, и вы сможете быстро воспроизвести ошибку компиляции.

Примечание сбоку:

Ваша фактическая проблема здесь не имеет ничего общего с гарантированным исключением копий, введенным в 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:

Инициализация массива в фигурных скобках иногда завершается неудачей, если нет конструктора копирования (gcc.gnu.org/bugzilla )

На момент написания статьи это было исправлено для GCC 11.0, 10.3 и 9.4, но проявляется в текущих версиях до 10.2 включительно.

Уменьшенный тестовый набор благодаря Джонатану Уэйкли:

 struct NonCopyable {
  NonCopyable(const NonCopyableamp;) = delete;
  NonCopyable(NonCopyableamp;amp;) = delete;
  NonCopyableamp; operator=(const NonCopyableamp;) = delete;
  NonCopyableamp; operator=(NonCopyableamp;amp;) = delete;

  NonCopyable() {}

  ~NonCopyable() {} // makes the destructor non-trivial
};

struct A {
  A(): _a{} {}
  ~A() {}

  NonCopyable _a[5];
} a;
 

Это работает с clang, сбой с GCC (см. Это на GodBolt).

Комментарии:

1. Спасибо за обновление! Только теория: они поспешили исправить это, поскольку Питер Димов из Boost тоже упомянул о своей встрече с этим … 🙂

2. @Secundi: Что? Я думал, что все это на моей учетной записи: -P