C++ 替换不可复制、不可移动的对象

C++ 替换不可复制、不可移动的对象,c++,c++11,new-operator,c++14,placement-new,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

考虑以下代码:

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

可能的解决方案
  • 动态分配
    奇怪的
    对象

    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
      )除了使代码更具可读性之外,只能解决最后一点

  • 使用新的放置方式

    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
      的析构函数会释放
      东西所占用的内存吗

      我认为,就像构造函数一样,析构函数不负责内存管理。此外,我的代码似乎运行良好。不过,我想澄清这一点

    • 内存对齐呢

      我的猜测是,由于它替换了同一类型的现有对象,内存已经对齐了。这是正确的吗

    • Container
      的析构函数会负责销毁
      东西吗

  • 问题 除了证实和/或反驳我上面解释的担忧外,我想知道是否还有其他选择。如果是,请给出一个示例实现


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

    编辑 我刚刚意识到,不必从我的自定义迭代器返回指向实际数据(封装映射的数据)的成员自定义对的引用和指针,我可以返回就地构造的自定义对(即
    奇怪的
    对象)。通常,我们看不到自己在洞穴中,而不是离开洞穴,继续前进:)。请原谅,我会将问题标记为“已关闭”。

    (如果我们讨论的是移动对象并使用
    auto
    关键字,您应该在问题中添加
    c++11
    标记)

    我不确定我是否真的理解你的问题;你举的例子我觉得不太好;在
    奇怪的
    中使用指针会更好。 例如,这可以编译并工作得非常好,并且在功能上与我认为的您想要做的相同

    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++编译)

    因为
    thing
    包含自引用,所以在您发布的代码中,您不需要在复制赋值运算符中对其进行任何操作。@t.C.您是对的,我的示例被破坏了。阅读最后一段可以帮助理解我的问题,同时我尝试提供一个更好的示例。好的,placement new要求您在需要时手动调用析构函数。否则它就不干净了。另外,你能宣布复制和移动构造函数被删除吗?@sukhmel谢谢,这正是我所怀疑的:)。赋值运算符,以及通常修改
    容器
    ,是必要的,因为实际上,它是一个自定义迭代器,用于包装
    std::unordered_映射
    。由于我还想为自己的对提供命名成员,迭代器也必须封装它们。但是,当迭代器递增时,引用必须更改。@Kalrish:如果您确实想更改它们引用的内容,您最好使用指针,而不是试图强制引用的行为与引用不同。您没有理解我的观点,即
    Strange
    必须包含引用-正如我在上面的一条评论中解释的那样,为了保持一致性,但我没有提供适当的示例。作为一个完全不相关的注释,您应该使用
    NULL
    或(最好)使用
    NULL ptr
    而不是
    0
    。谢谢你的回答:)。我不确定nullptr是否正确,因为没有c++11标记。那么,我的问题是,为什么不使用指针呢?我已经按照你的建议编辑了这个问题。至于你的问题,我正在开发一个自定义类,其功能与
    std::unordered_map
    类似。问题来自迭代器和迭代器对。我希望我的配对有命名的成员,即不是
    first
    second
    。为了实现这一点,我的自定义对只包含引用-因此它们就像真实的对。但是,由于迭代器必须返回引用和指针(运算符*和运算符->),这些自定义对必须存储在某个位置(在每个迭代器中)。当它被递增或复制时……那么所有这些麻烦仅仅是以不同的方式命名
    std::pair
    的成员?你认为值得吗?您可以在处理迭代器的上下文中定义一个角色,该迭代器提供另一个“命名空间”(字面上,而不是C++的)。我认为这样会更简单、更干净。:)因为它不是什么专业的东西,所以它和愉悦一样值得。所以或多或少无论如何,应该有不同的命名方法。它们不是很具有描述性。