【问题标题】:Use of placement-new operator and copy constructor instead of assignment operator使用placement-new 运算符和复制构造函数代替赋值运算符
【发布时间】:2017-01-14 01:47:45
【问题描述】:

我在使用无法更改的第 3 方代码时发现了一个问题。我需要制作对象成员的副本。我不能严格执行此操作,因为内部成员之一具有私有赋值运算符。我找到的唯一解决方案很棘手,所以我想问你是否看到任何可能影响我的程序的红灯。

这是我正在处理的简化代码(请记住,我无法更改它!):

#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;
}

我的解决方案是手动调用 Content 析构函数以释放 Content 及其内部类中任何动态分配的内存。堆栈上的内存保持不变,因此我可以使用placement-new 运算符重用它,该运算符调用存在的复制构造函数,并且完全符合我的需要。当主函数范围结束时,'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()

我不想创建自己的函数来复制所有字段,因为此类可以在新版本中更新,并且可能有其他字段我不会复制并且很可能没有人会记得修复它。

问题:您认为这会导致内存泄漏或内存损坏吗?你有没有看到我没有提到的问题?

【问题讨论】:

  • 改为使用指向Content的智能指针。很容易用新的指针替换它的指针。
  • 我没有看到任何问题本身,只要确保复制构造函数保持最新,否则你会遇到问题.......我有实际上之前使用过类似的策略
  • 当你copyContent(x,x)时会发生什么?
  • 乍一看,该库似乎是为该类设计的,不可分配,您无法预测可能发生或不发生的情况。您确定您的代码/设计需要分配此类型,或者您是否可以使用其他机制?
  • @Cheersandhth.-Alf 我希望我能做到。我无法修改示例中的任何类:A、B、Content、InnerContent(所有这些都来自库)。我使用指向 A 和 B 实例的智能指针。

标签: c++ copy-constructor assignment-operator placement-new


【解决方案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();
}

易于阅读,无论何时要分配内容对象,您都不会忘记析构函数调用。

【讨论】:

  • 感谢您的回复。 A 类和 B 类也在库中,所以我不能对它们的成员应用包装器,但我喜欢这个想法。
  • @SzymonKordyaczny 然后你可以尝试包装AB 而不是Content。顺便说一句,我刚看到一个错误,很快就会编辑我的答案。
猜你喜欢
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多