Использование оператора размещения-нового и конструктора копирования вместо оператора присваивания

Я обнаружил проблему при использовании стороннего кода, который нельзя изменить. Мне нужно сделать копию члена объекта. Я не могу сделать это строго потому, что у одного из внутренних членов есть частный оператор присваивания. Единственное решение, которое я нашел, сложное, поэтому я хочу спросить вас, видите ли вы какие-либо красные огни, которые могут повлиять на мою программу.

Вот упрощенный код, с которым я имею дело (помните, что я не могу его изменить!):

#include <iostream>
#include <algorithm>

class MBool
{
public:
    MBool() {};
    MBool(const MBool& arg) {}
private:    
    MBool& operator=(const MBool& arg);
};

class InnerContent {
private:
    int* pBuffer;

public: 
    InnerContent() {
        pBuffer = new int[20];
        std::cout << "InnerContent()" << std::endl;
    }

    InnerContent(const InnerContent& otherInnerContent) {
        pBuffer = new int[20];
        std::copy(otherInnerContent.pBuffer, otherInnerContent.pBuffer + 20, pBuffer);
        std::cout << "InnerContent(const InnerContent&)" << std::endl;
    }

    ~InnerContent() {
        std::cout << "~InnerContent()" << std::endl;
        delete [] pBuffer;
        pBuffer = nullptr;
    }

    virtual void someVirtualFunction() {}
};

class Content {
public:
    InnerContent innerContent;
    int someNumber;
    MBool boolVar;

    Content() {
        std::cout << "Content()" << std::endl;
    }
    ~Content() {
        std::cout << "~Content()" << std::endl;
    }
    Content(const Content& otherContent) :
        innerContent(otherContent.innerContent),
        someNumber(otherContent.someNumber),
        boolVar(otherContent.boolVar)
    {
        std::cout << "Content(const Content&)" << std::endl;
    }

    virtual void someVirtualFunction() {}
};

class A {
public: 
    Content content;

    A() { std::cout << "A()" << std::endl; }
    ~A() { std::cout << "~A()" << std::endl; }
};

class B {
public: 
    Content content;

    B() { std::cout << "B()" << std::endl; }
    ~B() { std::cout << "~B()" << std::endl; }
};

И вот что я собираюсь с ним сделать (только этот код можно изменить и расширить):

void copyContent(Content& contentFrom, Content& contentTo) {
    contentTo.~Content();
    new (&contentTo) Content(contentFrom);
};

int main() {
    A a;
    B b;

    // I wish to do this:
    //b.content = a.content;
    // but Content class has no operator= function implemented
    // also I can't use generated assignment operator function because of MBool::operator= is private

    // The only work-around I found is this:

    std::cout << "--- Before copying" << std::endl;
    copyContent(a.content, b.content);
    std::cout << "--- After copying" << std::endl;
}

Мое решение состоит в том, чтобы вызвать деструктор содержимого вручную, чтобы освободить любую динамически выделенную память в содержимом и его внутренних классах. Память в стеке остается нетронутой, поэтому я могу повторно использовать ее с оператором размещения-нового, который вызывает существующий конструктор копирования и делает именно то, что мне нужно. Когда область действия основной функции заканчивается, объект «a» очищается должным образом.

Вывод кода:

InnerContent()
Content()
A()
InnerContent()
Content()
B()
--- Before copying
~Content()
~InnerContent()
InnerContent(const InnerContent&)
Content(const Content&)
--- After copying
~B()
~Content()
~InnerContent()
~A()
~Content()
~InnerContent()

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

Вопрос. Как вы думаете, может ли это привести к утечке или повреждению памяти? Вы видите проблемы, о которых я не упомянул?


person Szymon Kordyaczny    schedule 06.09.2016    source источник
comment
Вместо этого просто используйте интеллектуальный указатель на Content. Легко заменить его пуант на новый.   -  person Cheers and hth. - Alf    schedule 06.09.2016
comment
Я не вижу никаких проблем само по себе, просто убедитесь, что конструктор копирования остается в актуальном состоянии, иначе у вас будут проблемы...... Я действительно использовал аналогичные стратегии раньше   -  person DarthRubik    schedule 06.09.2016
comment
Что происходит, когда вы делаете copyContent(x,x)?   -  person Barry    schedule 06.09.2016
comment
На первый взгляд кажется, что библиотека предназначена для того, чтобы этот класс нельзя было назначать, и вы не можете предсказать, что может или не может произойти. Вы уверены, что ваш код/дизайн требует назначения этого типа, или, возможно, есть альтернативный механизм, который вы могли бы использовать?   -  person Mark B    schedule 06.09.2016
comment
@Cheersandhth.-Alf Хотел бы я это сделать. Я не могу изменить ни один из классов из примера: A, B, Content, InnerContent (все они взяты из библиотеки). Я использую интеллектуальные указатели на экземпляры A и B.   -  person Szymon Kordyaczny    schedule 06.09.2016
comment
Конструктор копирования @DarthRubik должен постоянно обновляться, так как я думаю, что они автоматически генерируют его.   -  person Szymon Kordyaczny    schedule 06.09.2016
comment
@Barry, это вызовет ошибку сегментации, как только std::copy получит доступ к nullptr в pBuffer, спасибо, что указали на это   -  person Szymon Kordyaczny    schedule 06.09.2016
comment
@MarkB A и B на самом деле являются типами сообщений, я должен обрабатывать их одинаково, но я не хочу каким-либо образом дублировать код обработки сообщений. Сначала я хотел просто сосредоточиться на элементе Content в обоих случаях, но я сдался, потому что это сообщение обрабатывается асинхронно разными модулями, и для его выживания требуются интеллектуальные указатели A и B, пока вся обработка не будет завершена.   -  person Szymon Kordyaczny    schedule 06.09.2016


Ответы (1)


В принципе идея должна работать. Чтобы защитить себя от того, что вы забыли вызвать деструктор, я думаю, вы должны обернуть все мысли в виде умного указателя, такого как шаблон класса. В этом примере он на самом деле оборачивает не указатель, а сам объект содержимого.

template <typename ContentType>
class content_wrapper {
    private:
        ContentType content_;
    public:
        content_wrapper() : content_ {} {};
        content_wrapper(const content_wrapper& other) :
            content_{other.content_} {};

        content_wrapper& operator = (const content_wrapper& other) {
            content_.~ContentType();
            new (&content_) ContentType(other);
            return *this;
        }

        ContentWrapper& operator * () {
            return content_;
        }
        ContentWrapper* operator -> () {
            return &content_;
        }
};

теперь вы можете использовать его так:

class A {
    public: 
        content_wrapper<Content> content;

        A() { std::cout << "A()" << std::endl; }
        ~A() { std::cout << "~A()" << std::endl; }
};

class B {
    public: 
        content_wrapper<Content> content;

        B() { std::cout << "B()" << std::endl; }
        ~B() { std::cout << "~B()" << std::endl; }
};

int main() {
    A a;
    B b;

    b.content = a.content; // the wrapper will take care.

    b.content->someVirtualFunction();
}

Легко читается, и вы никогда не забудете вызов деструктора, когда захотите назначить объект содержимого.

person cdonat    schedule 06.09.2016
comment
Спасибо за ответ. Классы A и B тоже есть в библиотеке, поэтому я не могу применить обертку к их членам, но идея мне нравится. - person Szymon Kordyaczny; 06.09.2016
comment
@SzymonKordyaczny Тогда вы можете попробовать обернуть A и B вместо Content. Кстати, я только что увидел ошибку и скоро отредактирую свой ответ. - person cdonat; 06.09.2016