替换不可复制的不可移动对象

时间:2014-09-01 12:23:36

标签: c++ c++11 new-operator c++14 placement-new

请考虑以下代码:

// A non-copyable, non-movable aggregate
struct Strange
{
    const int & i;
    char & c;
};

class Container
{
    private:
        int  my_i;
        char my_c;
        Strange thing;

    public:
        // Valid, because both `my_i´ and `my_c´ are non-const
        // objects to which both references can be bound.
        explicit
            Container
            ( )
            noexcept
            : thing{ my_i , my_c }
            { }

        // How could this be implemented?
        auto &
            operator=
            ( const Container & that )
            noexcept
            {
                this->my_i = that->my_i;
                this->my_c = that->my_c;

                // What to do with `thing´?

                return *this;
            }
};

可能的解决方案

  1. 动态分配Strange对象

    class Container
    {
        private:
            int  my_i;
            char my_c;
            Strange * thing;
    
        public:
            // Note that it isn't exception safe.
            explicit
                Container
                ( )
                : thing(new Strange{ my_i , my_c })
                { }
    
            auto &
                operator=
                ( const Container & that )
                noexcept
                {
                    this->my_i = that->my_i;
                    this->my_c = that->my_c;
    
                    delete this->thing;
                    this->thing = new Strange { this->my_i , this->my_c };
    
                    return *this;
                }
    };
    

    关注:

    • 效率不高。
    • 不安全:分配可能会失败并抛出。
    • 危险:必须非常小心,不要泄漏记忆。

      除了使代码更具可读性之外,使用智能指针(即std::unique_ptr)只会解决最后一点。

  2. 使用展示位置

    class Container
    {
        private:
            int  my_i;
            char my_c;
            Strange thing;
    
        public:
            explicit
                Container
                ( )
                noexcept
                : thing{ my_i , my_c }
                { }
    
            auto &
                operator=
                ( const Container & that )
                noexcept
                {
                    this->my_i = that.my_i;
                    this->my_c = that.my_c;
    
                    // Placement new is exception safe, and so is
                    // construction of `Strange´.
                    this->thing.~Strange();
                    new(&this->thing) Strange { this->my_i , this->my_c };
    
                    return *this;
                }
    };
    

    关注:

    • Strange的析构函数会释放thing占用的内存吗?

      我认为,就像构造函数一样,析构函数不负责内存管理。而且,我的代码似乎工作正常。但是,我想澄清一下。

    • 内存对齐怎么样?

      我的猜测是,由于它取代了相同类型的现有对象,因此内存已经对齐。这是对的吗?

    • Container的析构函数是否会破坏thing

  3. 问题

    除了证实和/或驳斥我上面解释的问题之外,我想知道是否还有其他选择。如果是,请举例说明。


    这个问题出现在一个应该提供类似于std::unordered_map的界面的类上。我的类不是重新实现它,而是封装了这样的容器,只是充当了大多数方法的代理:它的迭代器包含了map提供的那些,它的对是一个聚合结构,具有适当命名的成员(是对它的引用)实际数据),在Strange提供的示例中表示。由于迭代器需要返回对实际数据的引用和指针,因此我的自定义迭代器包含一对。问题是修改它(当递增或分配迭代器时)。我承认这可能不是一个好主意,并且那些 会影响性能,但无论如何我对此事感兴趣。

    修改

    我刚刚意识到,不是从我的自定义迭代器返回指向指向实际数据(封装映射的数据)的成员自定义对的引用和指针,而是可以返回就地构造的自定义对(即{{ 1}}对象)。通常,我们没有看到我们在一个山洞里,而不是退出它,继续前进:)。请原谅我的噪音,我将问题标记为"已关闭"。

1 个答案:

答案 0 :(得分:1)

(如果我们正在谈论移动对象并使用auto关键字,则应在问题中添加c++11标记。

我不确定我真的理解你的问题;你给我的例子似乎没有想到我;在Strange中使用指针会好得多。 例如,这编译并且工作得非常好,并且在功能上等同于我想要做的事情。

struct Strange
{
    Strange()
        : i(nullptr), c(nullptr) {}
    Strange( const int *_i, const char *_c )
        : i(_i), c(_c) {}

    const int *i;
    const char *c;
};

class Container
{
    int  my_i;
    char my_c;
    Strange thing;

    public:

        Container()
            : thing(&my_i,&my_c)
            { }

        Container( int i, char c )
            : my_i(i), my_c(c), thing(&my_i,&my_c)
            { }

        Container( int i, char c, const Strange& s )
            : my_i(i), my_c(c), thing(s) // use default copy-constructor
            { }

        Container &
            operator=
            ( const Container & that )
            {
                my_i  = that.my_i;
                my_c  = that.my_c;
                thing = that.thing;

                return *this;
            }
};

int main()
{
    Container a(12,24);
    Container b(25,42);
    b = a;
}

请注意,引用对象内存通常很危险。 例如,在此使用memcpy将是一种灾难。 (用clang和g ++编译)