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++的)。我认为这样会更简单、更干净。:)因为它不是什么专业的东西,所以它和愉悦一样值得。所以或多或少无论如何,应该有不同的命名方法。它们不是很具有描述性。