Каковы преимущества boost::noncopyable
Чтобы предотвратить копирование класса, вы можете очень легко объявить закрытый конструктор копирования / операторы присваивания. Но вы также можете наследовать boost::noncopyable
,
Каковы преимущества / недостатки использования наддува в этом случае?
11 ответов
Подводя итог тому, что сказали другие:
Преимущества boost::noncopyable
по частным методам копирования:
- Это более явно и описательно в намерении. Использование функций частного копирования - это идиома, которая занимает больше времени, чем обнаружение
noncopyable
, - Это меньше кода / меньше ввода / меньше беспорядка / меньше места для ошибок (проще всего было бы случайно обеспечить реализацию).
- Он встраивает значение прямо в метаданные типа, аналогично атрибуту C#. Теперь вы можете написать функцию, которая принимает только объекты, которые не могут быть скопированы.
- Это потенциально ловит ошибки ранее в процессе сборки. Ошибка будет отображаться во время компиляции, а не во время компоновки, в случае, если сам класс или его друзья делают ошибочное копирование.
- (почти так же, как #4) Предотвращает сам класс или его друзей от вызова методов private copy.
Преимущества методов частного копирования перед boost::noncopyable
:
- Нет повышения зависимости
Я не вижу никакой выгоды от документации:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
против:
struct A
{
A(const A&) = delete;
A& operator=(const A&) = delete;
};
Когда вы добавляете типы только для перемещения, я даже вижу документацию вводящей в заблуждение. Следующие два примера не могут быть скопированы, хотя они являются подвижными:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
A(A&&) = default;
A& operator=(A&&) = default;
};
против:
struct A
{
A(A&&) = default;
A& operator=(A&&) = default;
};
При множественном наследовании может быть даже штраф за пробел:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
struct B
: public A
{
B();
B(const B&);
B& operator=(const B&);
};
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 A&) = delete;
A& operator=(const A&) = delete;
};
struct B
: public A
{
B();
B(const B&);
B& operator=(const B&);
};
struct C
: public A
{
C(const C&) = delete;
C& operator=(const C&) = delete;
};
struct D
: public B,
public C
{
D(const D&) = delete;
D& operator=(const D&) = delete;
};
#include <iostream>
int main()
{
std::cout << sizeof(D) << '\n';
}
Выходы:
2
Мне гораздо проще объявить свои операции копирования, чем рассуждать, извлекаю ли я из boost::non_copyable
несколько раз, и если это будет стоить мне. Особенно, если я не являюсь автором полной иерархии наследования.
Это делает намерение явным и ясным, в противном случае необходимо увидеть определение класса и найти объявление, относящееся к семантике копирования, а затем найти спецификатор доступа, в котором оно объявлено, чтобы определить, является ли класс не копируемый или нет. Другой способ обнаружить это - написать код, для которого требуется включить семантику копирования и увидеть ошибку компиляции.
- Цель boost::noncopyable более ясна.
- Boost:: noncopyable предотвращает случайное использование методов классов конструктором закрытого копирования.
- Меньше кода с boost::noncopyable.
Я не могу понять, почему никто другой, кажется, не упоминает об этом, но:
С noncopyable
Вы пишете название своего класса только один раз.
Без пятикратного дублирования: один A для "класса A", два для отключения назначения и два для отключения конструктора копирования.
Цитирование документации:
"Традиционный способ справиться с этим - объявить конструктор частной копии и присвоение копии, а затем задокументировать, почему это делается. Но вывод из некопируемого проще и понятнее, и не требует дополнительной документации".
Одно конкретное преимущество (помимо более ясного выражения своих намерений) состоит в том, что ошибка будет обнаружена раньше, на этапе компиляции, а не на этапе компоновки, если функция-член или функция-друг пытается скопировать объект. Конструктор / присваивание базового класса нигде не доступны, что приводит к ошибке компиляции.
Это также предотвращает случайное определение функций (т.е. {}
вместо ;
), небольшая ошибка, которая может остаться незамеченной, но которая позволит членам и друзьям делать недействительные копии объекта.
Небольшой недостаток (специфичный для GCC) заключается в том, что если вы компилируете свою программу с g++ -Weffc++
и у вас есть классы, содержащие указатели, например,
class C : boost::noncopyable
{
public:
C() : p(nullptr) {}
private:
int *p;
};
GCC не понимает, что происходит:
предупреждение: "класс C" имеет указатели на данные-члены [-WeffC++]
предупреждение: но не переопределяет 'C(const S&)' [-WeffC++]
предупреждение: или 'operator=(const C&)' [-WeffC++]
Пока не буду жаловаться на:
#define DISALLOW_COPY_AND_ASSIGN(Class) \
Class(const Class &) = delete; \
Class &operator=(const Class &) = delete
class C
{
public:
C() : p(nullptr) {}
DISALLOW_COPY_AND_ASSIGN(C);
private:
int *p;
};
PS Я знаю, что у GCC -WeffC++ есть несколько проблем. Код, который проверяет "проблемы", довольно упрощен, во всяком случае... иногда это помогает.
Преимущество заключается в том, что вам не нужно самим создавать конструктор личных копий и оператор частного копирования, и это четко выражает ваше намерение без написания дополнительной документации.
Я бы лучше использовал boost:: noncopyable, чем вручную удалять или приватизировать конструктор копирования и оператор присваивания.
Тем не менее, я почти никогда не использую ни один из этих методов, потому что:
Если я делаю не копируемый объект, должна быть причина, по которой он не копируется. Эта причина, в 99% случаев, в том, что у меня есть участники, которые не могут быть скопированы осмысленно. Скорее всего, такие члены также будут лучше подходить как частные детали реализации. Так что я делаю большинство таких классов, как это:
struct Whatever {
Whatever();
~Whatever();
private:
struct Detail;
std::unique_ptr<Detail> detail;
};
Итак, теперь у меня есть частная структура реализации, и, поскольку я использовал std::unique_ptr, мой класс верхнего уровня не подлежит копированию бесплатно. Исходящие из этого ошибки ссылок понятны, потому что они говорят о том, что вы не можете скопировать std::unique_ptr. Для меня это все преимущества boost:: noncopyable и частной реализации, объединенной в одну.
Преимущество этого шаблона в дальнейшем, если я решу, что действительно хочу сделать мои объекты этого класса копируемыми, я могу просто добавить и реализовать конструктор копирования и / или оператор присваивания без изменения иерархии классов.
Недостаток, по словам Скотта Мейерса, имя "ненатруальное", если вам нужно найти его.