具有const成员的类的赋值
assignment of class with const member
考虑以下代码:
struct s
{
const int id;
s(int _id):
id(_id)
{}
};
// ...
vector<s> v; v.push_back(s(1));
我收到一个编译器错误,"const int id"不能使用默认赋值运算符。
Q1.为什么push_back()需要赋值运算符
A1.因为目前的c++标准是这么说的。
Q2.我该怎么办?
- 我不想放弃const说明符
- 我想复制数据
A2.我会使用智能指针。
Q3.我想出了一个"解决方案",看起来相当疯狂:
s& operator =(const s& m)
{
if(this == &m) return *this;
this->~s();
return *new(this) s(m);
}
我应该避免这种情况吗?为什么(如果是的话)如果对象在堆栈上,使用placement new是否安全
C++03要求存储在容器中的元素是CopyConstructible
和Assignable
(请参见§23.1)。因此,实现可以根据需要决定使用复制构造和赋值。这些约束在C++11中得到了放松。明确地说,push_back
操作要求是将类型CopyInsertable
放入向量中(见§23.2.3序列容器)
此外,C++11容器可以在插入操作中使用move语义。
我不想放弃常量说明符
你别无选择。
s& operator =(const s& m) {
return *new(this) s(m);
}
未定义的行为。
几乎没有人使用const
成员变量是有原因的,正因为如此。你对此无能为力。const
成员变量根本不能用于你想要可赋值的类型。这些类型是不可变的,仅此而已,vector
的实现需要可变性。
s& operator =(const s& m) { if(this == &m) return *this; this->~s(); return *new(this) s(m); }
我应该避免这种情况吗?为什么(如果是的话)?如果对象在堆栈上,使用新放置是否安全?
如果可以的话,你应该避免它,不是因为它格式不正确,而是因为读者很难理解你的目标并信任这段代码。作为一名程序员,你应该致力于减少你编写的WTF/行代码的数量。
但是,这是合法的。根据
[new.delete.placement]/3
void* operator new(std::size_t size, void* ptr) noexcept;
3备注:故意不采取其他行动。
调用placement new不会分配或释放内存,相当于手动调用s
的复制构造函数,根据[basic.life]/8
,如果s
有一个平凡的析构函数,则复制构造函数是合法的。
好的,
你应该总是用简单的步骤来思考问题。
std::vector<typename T>::push_back(args);
需要在矢量数据中保留空间,然后将参数值分配(或复制或移动)到该位置的vector.data()[idx]的内存中。
要理解为什么不能在成员函数std::vector::push_back中使用结构,请尝试以下操作:
std::vector<const int> v; // the compiler will hate you here,
// because this is considered ill formed.
格式错误的原因是类std::vector的成员函数可以调用其模板参数的赋值运算符,但在这种情况下,它是一个常量类型参数"const-int",这意味着它没有赋值运算符(给常量变量赋值毫无意义!!)。对于具有const数据成员的类类型,也观察到了相同的行为。因为编译器会删除默认的赋值运算符,所以会驱逐
struct S
{
const int _id; // automatically the default assignment operator is
// delete i.e. S& operator-(const S&) = delete;
};
// ... that's why you cannot do this
std::vector<S> v;
v.Push_back(S(1234));
但是,如果你想保持意图并用一个格式良好的代码来表达它,你应该这样做:
class s
{
int _id;
public:
explicit s(const int& id) :
_id(id)
{};
const int& get() const
{
return _id;
}; // no user can modify the member variable after it's initialization
};
// this is called data encapsulation, basic technique!
// ...
std::vector<S> v ;
v.push_back(S(1234)); // in place construction
如果你想打破规则,强加一个可赋值的常量类类型,那么就按照上面的建议去做。
Q2.我该怎么办?
存储指针,最好是智能的。
vector<unique_ptr<s>> v;
v.emplace_back(new s(1));
这不是一个真正的解决方案,而是一个变通方法:
#include <vector>
struct s
{
const int id;
s(int _id):
id(_id)
{}
};
int main(){
std::vector<s*> v;
v.push_back(new s(1));
return 0;
}
这将存储s
的指针,而不是对象本身。至少它编译了…;)
编辑:您可以使用智能c++11指针来增强这一点。请参阅Benjamin Lindley的回答。
在赋值运算符中使用const_cast:
S& operator=(const S& rhs)
{
if(this==&rhs) return *this;
int *pid=const_cast<int*>(&this->id);
*pid=rhs.id;
return *this;
}
- 为什么我不能在返回 const 的布尔函数中为类成员变量赋值?C++
- 复制赋值函数如何访问另一个对象的私有成员(Stroustroup 原则和实践书)?
- 清除具有已删除赋值运算符的成员的类实例
- 如果类在 C++ 中具有常量或引用类型的非静态数据成员,为什么编译器不提供默认赋值运算符?
- 静态成员函数赋值而不带返回语句
- 我们可以直接为任何数据成员赋值. 为什么要使用构造函数?
- 使用 boost python 从 c++ 为 python 中的类成员变量赋值
- Clang 无法在赋值运算符/复制构造函数中检测到未初始化的类成员
- 通过智能指针向类成员进行赋值
- 是否应该在复制构造函数或赋值运算符中复制静态数据成员
- Gcc 使用 memcpy 作为隐式复制赋值运算符,而不是成员复制
- C++ 包含唯一指针成员变量的类的赋值运算符
- 是否有必要重载具有另一个类 B 的数据成员的类 A 的赋值运算符和复制构造函数?
- 我们不能在未赋值的上下文中命名非静态成员函数是有原因的吗
- 通过迭代器为映射的成员赋值
- 为什么不能为基类的成员赋值?
- 如何使用其他函数返回的值为结构体成员赋值
- 什么是成员数据指针的正确赋值语句
- STL - 赋值运算符与“赋值”成员函数
- 在可选结构成员中赋值成员