#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
Ответ №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