Вызовите конструктор копирования по умолчанию изнутри перегруженного конструктора копирования - PullRequest
11 голосов
/ 04 сентября 2011

Мне нужно написать конструктор копирования, который копирует содержимое std::shared_ptr. Однако в классе также определена группа переменных int a, b, c, d, e;. Есть ли способ сгенерировать код конструктора копирования по умолчанию (или вызвать конструктор копирования по умолчанию) внутри моего нового перегруженного.

Вот фрагмент кода с комментарием, который, мы надеемся, прояснит проблему.

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(*other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

Ответы [ 6 ]

7 голосов
/ 04 сентября 2011

Я всегда думаю, что подобные вопросы должны иметь хотя бы одну цитату ответа из стандарта для будущих читателей, так что вот оно.

§12.8.4 стандарта гласит, что:

Если определение класса явно не объявляет конструктор копирования, он объявляется неявно.

Это означает, что когда определение класса действительно явно объявляет конструктор копирования,один не объявлен неявно.Поэтому, если вы объявляете одно явно, неявное не существует, поэтому вы не можете его назвать.

5 голосов
/ 04 сентября 2011

Вот код вопроса, когда я пишу это:

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

Скорее всего, приведенный выше код неправильный , потому что

  1. конструктор по умолчанию оставляет a, b, c, d и e неинициализированными, а

  2. код не отвечает за копирование назначения, и

  3. выражение new Bla(other.p) требует, чтобы у Bla был конструктор, принимающий std::shared_ptr<Bla>, что крайне маловероятно.

С std::shared_ptr это должен быть код C ++ 11, чтобы быть формально правильным в отношении языка.Тем не менее, я считаю, что это просто код, который использует то, что доступно с вашим компилятором.Поэтому я считаю, что соответствующим стандартом C ++ является C ++ 98 с техническими исправлениями поправки C ++ 03.

Вы можете легко использовать встроенную (сгенерированную) инициализацию копирования, даже в C++ 98, например,

namespace detail {
    struct AutoClonedBla {
        std::shared_ptr<Bla> p;

        AutoClonedBla( Bla* pNew ): p( pNew ) {}

        AutoClonedBla( AutoClonedBla const& other )
            : p( new Bla( *other.p ) )
        {}

        void swap( AutoClonedBla& other )
        {
            using std::swap;
            swap( p, other.p );
        }

        AutoClonedBla& operator=( AutoClonedBla other )
        {
            other.swap( *this );
            return *this;
        }
    };
}

class Foo {
public:
     Foo(): a(), b(), c(), d(), e(), autoP( new Bla ) {}
     // Copy constructor generated by compiler, OK.

private:
     int                      a, b, c, d, e;
     detail::AutoClonedBla    autoP;
};

Обратите внимание, что этот код правильно инициализируется в конструкторе по умолчанию, отвечает за назначение копирования (для этого используется swap idiom ) и нетребуется специальный конструктор Bla с поддержкой смарт-указателей, но вместо этого просто используется обычный конструктор копирования Bla для копирования.

4 голосов
/ 04 сентября 2011

Было бы проще написать вариант для shared_ptr, в который встроено глубокое копирование.Таким образом, вы не должны написать конструктор копирования для своего основного класса;только для этого специального deep_copy_shared_ptr типа.Ваш deep_copy_shared_ptr будет иметь конструктор копирования, и он будет хранить shared_ptr сам.Он может даже иметь неявное преобразование в shared_ptr, чтобы сделать его немного проще в использовании.

1 голос
/ 04 сентября 2011

Это невозможно. Либо вы пишете пользовательский конструктор копирования (полностью самостоятельно), либо компилятор пишет его для вас.

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

Кроме этого, я не совсем уверен, почему вы используете shared_ptr<>. Смысл shared_ptr<> состоит в том, чтобы несколько указателей могли безопасно указывать на один и тот же объект. Но вы не разделяете мнение, вы копируете его. Возможно, вам следует вместо этого использовать необработанный указатель и освободить его в деструкторе. Или, что еще лучше, замените shared_ptr<> на clone_ptr, а затем полностью исключите конструктор копирования, назначение копирования и деструктор.

1 голос
/ 04 сентября 2011

Насколько я знаю, но что вы можете (и должны) делать, так это использовать список инициализаторов:

Foo::Foo(Foo const & other)
    : a(other.a), b(other.b), c(other.c), 
      d(other.d), e(other.e), p(new Bla(other.p))
{}

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

И, кстати, комментарий Керрека прав.Зачем вам shared_ptr, если вы все равно делаете глубокую копию.В этом случае unique_ptr может быть более подходящим.Кроме того, его преимущества shared_ptr - это не общее решение «больше не нужно думать об освобождении», и вы всегда должны думать, нужен ли вам интеллектуальный указатель и какой тип интеллектуального указателя наиболее подходит.

0 голосов
/ 25 мая 2018

Оператор присваивания по умолчанию имеет тот же код, что и конструктор копирования по умолчанию. Хотя вы не можете вызвать конструктор копирования по умолчанию из вашей перегрузки, такой же код существует в assignemnt, поэтому.

Foo::Foo(Foo const & other) 
{
    *this = other;
    p.reset(new Bla(other.p));
}

должен получить то, что вам нужно.

Редактировать: не берите в голову, что это фактически тот же самый код, и явное объявление создателя копии предотвращает его генерацию: (

...