#c #boost #noncopyable
#c #boost #не копируемый
Вопрос:
Чтобы предотвратить копирование класса, вы можете очень легко объявить частный конструктор копирования / операторы присваивания. Но вы также можете наследовать boost::noncopyable
.
Каковы преимущества / недостатки использования boost в этом случае?
Комментарии:
1. Обратите внимание, что в C 11 вы бы написали
struct Foo{Foo(const Fooamp;)=delete;};
2. Я предполагаю, что это в основном потому, что средний пользователь не понимает, почему ваш конструктор копирования является закрытым и неопределенным.
3. @spraff Я полагаю, вам также понадобится
Foo amp; operator=(const Foo amp;) = delete;
?4. ДА. Это был пример, а не полная реализация.
Ответ №1:
Я не вижу никакой пользы от документации:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
против:
struct A
{
A(const Aamp;) = delete;
Aamp; operator=(const Aamp;) = delete;
};
Когда вы добавляете типы только для перемещения, я даже вижу, что документация вводит в заблуждение. Следующие два примера не копируются, хотя они являются подвижными:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
A(Aamp;amp;) = default;
Aamp; operator=(Aamp;amp;) = default;
};
против:
struct A
{
A(Aamp;amp;) = default;
Aamp; operator=(Aamp;amp;) = default;
};
При множественном наследовании может быть даже штраф за пробел:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
struct B
: public A
{
B();
B(const Bamp;);
Bamp; operator=(const Bamp;);
};
struct C
: public A
{
};
struct D
: public B,
public C,
private boost::noncopyable
{
};
#include <iostream>
int main()
{
std::cout << sizeof(D) << 'n';
}
Для меня это выводит:
3
Но это, я считаю, имеет превосходную документацию:
struct A
{
A(const Aamp;) = delete;
Aamp; operator=(const Aamp;) = delete;
};
struct B
: public A
{
B();
B(const Bamp;);
Bamp; operator=(const Bamp;);
};
struct C
: public A
{
C(const Camp;) = delete;
Camp; operator=(const Camp;) = delete;
};
struct D
: public B,
public C
{
D(const Damp;) = delete;
Damp; operator=(const Damp;) = delete;
};
#include <iostream>
int main()
{
std::cout << sizeof(D) << 'n';
}
Выводит:
2
Мне гораздо проще объявлять свои операции копирования, чем рассуждать о том, извлекаю ли я из boost::non_copyable
нескольких раз и будет ли это мне стоить. Особенно, если я не являюсь автором полной иерархии наследования.
Комментарии:
1. Честно говоря,
boost::noncopyable
был доступен задолго до C 11 и поддержки компиляции= delete
. Я согласен с вами, что с компиляторами, совместимыми с C 11, он устарел.2. У кого-то была хорошая идея и создать
noncopyable
базовый класс CRTP, чтобы все базовые классы в иерархии были уникальными.3. Другим недостатком является то, что
private: __copy_constructor__;
он полностью переносим, и вам не нужно ~ 40 МБ зависимостей Boost.4. В связи с этим возникает вопрос: что еще в boost устарело в C 11?
5. @Jon: на этот вопрос нет жестких и быстрых ответов. Однако (просто в качестве примера) Я бы подумал об использовании
std::vector<std::unique_ptr<animal>>
, прежде чем перейти кboost::ptr_vector<animal>
( boost.org/doc/libs/1_54_0/libs/ptr_container/doc/tutorial.html ). Обоснование: если я знаюvector
, и я знаюunique_ptr
, то я знаю семантику векторов unique_ptr . И я знаю, как std ::алгоритмы (например, sort) взаимодействуют с ним. Мне не нужно узнавать все о новом контейнере с его алгоритмами-членами (например, сортировка членов).
Ответ №2:
Резюмируя то, что сказали другие:
Преимущества boost::noncopyable
более частных методов копирования:
- Он более явный и описательный по замыслу. Использование частных функций копирования — это идиома, для определения которой требуется больше времени, чем
noncopyable
. - Меньше кода / меньше ввода / меньше беспорядка / меньше места для ошибок (проще всего было бы случайно предоставить реализацию).
- Он встраивает значение прямо в метаданные типа, подобно атрибуту C #. Теперь вы можете написать функцию, которая принимает только объекты, которые не подлежат копированию.
- Он потенциально улавливает ошибки ранее в процессе сборки. Ошибка будет представлена во время компиляции, а не во время компоновки, в случае, если сам класс или друзья класса выполняют ошибочное копирование.
- (почти так же, как # 4) Не позволяет самому классу или друзьям класса вызывать методы частного копирования.
Преимущества методов частного копирования по сравнению boost::noncopyable
:
- Нет зависимости от boost
Комментарии:
1. Существует также недостаток пространства, как указал @Howard Hinnant
Ответ №3:
Это делает намерение явным и ясным, в противном случае нужно увидеть определение класса и выполнить поиск объявления, связанного с семантикой копирования, а затем искать спецификатор доступа, в котором он объявлен, чтобы определить, является ли класс не копируемым или нет. Другой способ обнаружить это, написав код, для которого требуется включить семантику копирования, и увидеть ошибку компиляции.
Комментарии:
1. Вам не нужно видеть определение, чтобы увидеть, что оператор копирования является закрытым в объявлении.
2. @spraff: это называется определением класса. Определение класса содержит все объявленные члены.
3. Чтобы копнуть глубже, одна часть преимущества явности заключается в том, что значение теперь встроено в метаданные typename . Теперь вы могли бы написать функцию, которая принимает только объекты, не подлежащие копированию, например.
4. Если у вас нет доступа к определению класса, то это неполный тип, и вы не можете использовать его ни для чего . Без этого определения вы не можете видеть, что оно
noncopyable
также наследуется. Так что это спорный вопрос.5. @spraff: Я не понимаю, что вы подразумеваете под технической разницей. Я сказал что-нибудь в этом роде?
Ответ №4:
- Цель boost::noncopyable понятнее.
- Boost::noncopyable предотвращает случайное использование методами классов конструктора частного копирования.
- Меньше кода с boost::noncopyable.
Ответ №5:
Я не могу понять, почему никто больше не упоминает об этом, но:
При noncopyable
этом вы пишете имя своего класса только один раз.
Без пятикратного дублирования: один A для «класса A», два для отключения присваивания и два для отключения конструктора копирования.
Комментарии:
1. и вы говорите, что он не копируется, что повышает удобочитаемость и может быть найдено.
Ответ №6:
Цитирование документации:
«Традиционный способ справиться с этим — объявить частный конструктор копирования и присвоение копирования, а затем документировать, почему это делается. Но вывод из noncopyable проще и понятнее и не требует дополнительной документации. «
http://www.boost.org/libs/utility/utility.htm#Class_noncopyable
Ответ №7:
Одним из конкретных преимуществ (помимо более четкого выражения ваших намерений) является то, что ошибка будет обнаружена раньше, на этапе компиляции, а не на этапе ссылки, если функция-член или друг попытается скопировать объект. Конструктор / назначение базового класса нигде не доступны, что приводит к ошибке компиляции.
Это также предотвращает случайное определение функций (т. Е. Ввод {}
вместо ;
), небольшая ошибка, которая вполне может остаться незамеченной, но которая затем позволит участникам и друзьям создавать недопустимые копии объекта.
Комментарии:
1. @Mike:
...is that the error will be caught sooner, at the compile stage not the link stage
. Как именно? Evenboost::noncopyable
делает то же самое, что и вы, если бы вы его не использовали.2. @Nawaz: Если вы не используете
noncopyable
базовый класс, то вы объявляете частный конструктор в своем классе. Это доступно от членов и друзей класса, поэтому ошибки компиляции нет — просто ошибка ссылки из-за отсутствия определения. (Если вы случайно не предоставите определение — использование базового класса также предотвратит эту ошибку).3. Поскольку noncopyable имеет частные функции копирования, они вообще не могут быть доступны дочернему классу — таким образом, ошибка компилятора. Если вы помещаете функции в дочерний класс, к ним можно получить доступ, и, следовательно, они действительны до тех пор, пока компоновщик не увидит, что они не определены.
4. @MikeSeymour: Хорошо. Речь идет только о членах и друзьях. Я не думал о них. Хороший момент. Но с практической точки зрения это почти не имеет преимуществ, поскольку современная среда разработки или так называемый компилятор выполняет оба последовательно, что означает, что все вы получаете ошибки.
5. @Nawaz: Да, если вы создаете простую программу, то преимуществ мало. Если вы создаете большой проект, вам может потребоваться долгое ожидание, пока он попытается связать. Если вы создаете библиотеку, вы вообще не получите ошибок; они будут отложены до тех пор, пока вы не попытаетесь связать эту библиотеку с программой.
Ответ №8:
Небольшой недостаток (специфичный для GCC) заключается в том, что если вы компилируете свою программу с g -Weffc
помощью и у вас есть классы, содержащие указатели, например
class C : boost::noncopyable
{
public:
C() : p(nullptr) {}
private:
int *p;
};
GCC не понимает, что происходит:
предупреждение: ‘class C’ имеет элементы данных указателя [-Weffc ]
предупреждение: но не переопределяет ‘C (const Samp;)’ [-Weffc ]
предупреждение: или ‘operator= (const Camp;)’ [-Weffc ]
Хотя он не будет жаловаться на:
#define DISALLOW_COPY_AND_ASSIGN(Class)
Class(const Class amp;) = delete;
Class amp;operator=(const Class amp;) = delete
class C
{
public:
C() : p(nullptr) {}
DISALLOW_COPY_AND_ASSIGN(C);
private:
int *p;
};
PS Я знаю, что у GCC -Weffc есть несколько проблем. Код, который проверяет наличие «проблем», в любом случае довольно упрощен … иногда это помогает.
Ответ №9:
Преимущество заключается в том, что вам не нужно самостоятельно писать конструктор частного копирования и оператор частного копирования, и он четко выражает ваше намерение без написания дополнительной документации.
Ответ №10:
Я бы предпочел использовать boost::noncopyable, чем вручную удалять или приватизировать конструктор копирования и оператор присваивания.
Однако я почти никогда не использую ни один из методов, потому что:
Если я создаю объект, не подлежащий копированию, должна быть причина, по которой он не подлежит копированию. Эта причина в 99% случаев заключается в том, что у меня есть члены, которые нельзя скопировать осмысленно. Скорее всего, такие члены также лучше подходят в качестве частных деталей реализации. Поэтому я создаю большинство таких классов следующим образом:
struct Whatever {
Whatever();
~Whatever();
private:
struct Detail;
std::unique_ptr<Detail> detail;
};
Итак, теперь у меня есть частная структура реализации, и, поскольку я использовал std::unique_ptr, мой класс верхнего уровня не может быть скопирован бесплатно. Ошибки ссылок, возникающие из-за этого, понятны, потому что они говорят о том, как вы не можете скопировать std::unique_ptr . Для меня это все преимущества boost::noncopyable и частной реализации, объединенные в одно целое.
Преимущество этого шаблона заключается в том, что позже, если я решу, что действительно хочу сделать свои объекты этого класса копируемыми, я могу просто добавить и реализовать конструктор копирования и / или оператор присваивания без изменения иерархии классов.
Комментарии:
1. unique_ptr создает впечатление, что detail может быть нулевым.
2. Это может быть null unique_ptr, которого не может быть? По крайней мере, указатель с областью действия Boost имеет пустой конструктор для обработки null — не знаю о std::unique_ptr .
Ответ №11:
Отрицание, по словам Скотта Мейерса, название «ненатуральное», если вам действительно нужно найти его отрицание.