这是一个开放式的问题。 有效的C ++。第3项。尽可能使用const。真的吗?
我想做一些在对象生命周期const期间不会改变的东西。但const带来了它自己的麻烦。如果类具有任何const成员,则禁用编译器生成的赋值运算符。如果没有赋值运算符,则类将无法与STL一起使用。如果您想提供自己的赋值运算符,则需要 const_cast 。这意味着更多的喧嚣和更多的错误空间。你经常使用const类成员吗?
编辑:作为一项规则,我努力保持const正确性,因为我做了很多多线程。我很少需要为我的类实现复制控制,从不编写删除代码(除非绝对必要)。我觉得const的当前状态与我的编码风格相矛盾。 Const迫使我实现赋值运算符,即使我不需要它。即使没有 const_cast 分配也很麻烦。您需要确保所有const成员比较相等,然后手动复制所有非const成员。
代码。希望它能澄清我的意思。您在下面看到的课程不适用于STL。您需要为它实现一个赋值,即使您不需要它。
class Multiply {
public:
Multiply(double coef) : coef_(coef) {}
double operator()(double x) const {
return coef_*x;
}
private:
const double coef_;
};
答案 0 :(得分:18)
你自己说过你把const变成“在物体生命期间不会改变的东西”。然而,您抱怨隐式声明的赋值运算符被禁用。但隐式声明赋值运算符 会更改有问题成员的内容!完全合乎逻辑(根据您自己的逻辑),它正在被禁用。要么是,要么你不应该声明成员const。
此外,为您提供自己的赋值运算符不需要const_cast
。为什么?您是否正在尝试将分配给您在赋值运算符中声明为const的成员?如果是这样,为什么你声明它为const呢?
换句话说,提供您正在遇到的问题的更有意义的描述。到目前为止你提供的那个以最明显的方式是自相矛盾的。
答案 1 :(得分:5)
我很少使用它们 - 麻烦太大了。当然,在成员函数,参数或返回类型方面,我总是力求const正确性。
答案 2 :(得分:5)
正如AndreyT指出的那样,在这些情况下,分配(大多数)并没有多大意义。问题是vector
(例如)是该规则的一个例外。
逻辑上,您将对象复制到vector
,稍后您将获得原始对象的另一个副本。从纯粹的逻辑观点来看,不涉及任务。问题是vector
要求对象无论如何都是可分配的(实际上,所有C ++容器都可以)。它基本上是一个实现细节(在代码中的某个地方,它可能分配对象而不是复制它们)的一部分接口。
没有简单的治疗方法。即使定义自己的赋值运算符并使用const_cast
也无法解决问题。当您获得const_cast
指针或对您知道实际上未定义为const
的对象的引用时,使用const
是完全安全的。但是,在这种情况下,变量本身 被定义为const
- 试图抛弃const
并分配给它会给出未定义的行为。实际上,它几乎总是可以工作(只要它不是static const
,并且在编译时已经知道了初始化程序),但是不能保证它。
C ++ 11和更新版本为这种情况添加了一些新的曲折。特别是,不再需要需要的对象可以存储在向量(或其他集合)中。它们可以移动就足够了。这在这种特殊情况下没有帮助(移动const
对象比分配它更容易)但是在其他一些情况下确实使生活变得更加容易(例如,肯定有类型可移动但是不可转让/可复制的。)
在这种情况下,可以通过添加间接级别来使用移动而不是副本。如果你创建一个“外部”和一个“内部”对象,内部对象中有const
成员,外部对象只包含指向内部的指针:
struct outer {
struct inner {
const double coeff;
};
inner *i;
};
...然后当我们创建outer
的实例时,我们定义一个inner
对象来保存const
数据。当我们需要做一个赋值时,我们做一个典型的移动赋值:将指针从旧对象复制到新对象,并且(可能)将旧对象中的指针设置为nullptr,所以当它被销毁时,它就赢了试图摧毁内部物体。
如果你想要足够严重,你可以在旧版本的C ++中使用(排序)相同的技术。您仍然使用外部/内部类,但每个赋值将分配一个全新的内部对象,或者您使用类似shared_ptr的东西让外部实例共享对单个内部对象的访问权限,并在最后一个外部物体被摧毁。
它没有任何真正的区别,但至少对于管理向量时使用的赋值,只有inner
的两个引用,而vector
正在调整自身大小(调整大小)这就是为什么一个向量需要赋值可以开始的。)
答案 3 :(得分:4)
编译时的错误很痛苦,但运行时的错误是致命的。使用const的构造可能是代码的麻烦,但它可能会帮助您在实现它们之前找到错误。我尽可能使用consts。
答案 4 :(得分:3)
我尽可能遵循尽可能使用const
的建议,但我同意,当涉及到班级成员时,const
是一个很大的麻烦。
我发现我对const
非常小心 - 在参数方面是正确的,但对于类成员则没有那么多。实际上,当我创建类成员const
并导致错误(由于使用STL容器)时,我要做的第一件事就是删除const
。
答案 5 :(得分:3)
我想知道你的情况......以下所有内容都是假设,因为你没有提供描述你问题的示例代码,所以......
我猜你有类似的东西:
struct MyValue
{
int i ;
const int k ;
} ;
IIRC,默认赋值运算符将执行逐个成员的赋值,类似于:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
this->k = rhs.k ; // THIS WON'T WORK BECAUSE K IS CONST
return *this ;
} ;
因此,这不会生成。
所以,你的问题是如果没有这个赋值运算符,STL容器将不接受你的对象。
就我所见:
operator =
我害怕明白const_cast
是什么意思。
我自己的问题解决方案是编写以下用户定义的运算符:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
// DON'T COPY K. K IS CONST, SO IT SHOULD NO BE MODIFIED.
return *this ;
} ;
这样,如果你有:
MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 2 }
据我所知,它是连贯的。但我想,在阅读const_cast
解决方案时,您希望有更多类似的内容:
MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 20 } : K WAS COPIED
这意味着operator =
的以下代码:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
const_cast<int &>(this->k) = rhs.k ;
return *this ;
} ;
但是,你在问题中写道:
我想制作在对象生命周期期间不会改变的任何东西
我认为是你自己的const_cast
解决方案,k在对象生命周期中发生了变化,这意味着你自相矛盾,因为你需要一个在对象生命期内不会改变的成员变量除非你想改变 !
接受您的成员变量在其所有者对象的生命周期内将更改的事实,并删除const。
答案 6 :(得分:2)
如果您想保留shared_ptr
成员,可以将const
存储到STL容器中的const
个对象。
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include <vector>
class Fruit : boost::noncopyable
{
public:
Fruit(
const std::string& name
) :
_name( name )
{
}
void eat() const { std::cout << "eating " << _name << std::endl; }
private:
const std::string _name;
};
int
main()
{
typedef boost::shared_ptr<const Fruit> FruitPtr;
typedef std::vector<FruitPtr> FruitVector;
FruitVector fruits;
fruits.push_back( boost::make_shared<Fruit>("apple") );
fruits.push_back( boost::make_shared<Fruit>("banana") );
fruits.push_back( boost::make_shared<Fruit>("orange") );
fruits.push_back( boost::make_shared<Fruit>("pear") );
BOOST_FOREACH( const FruitPtr& fruit, fruits ) {
fruit->eat();
}
return 0;
}
但是,正如其他人已经指出的那样,如果您希望编译器生成复制构造函数,那么在我看来删除const限定成员通常会更容易麻烦。
答案 7 :(得分:1)
我只在引用或指针类成员上使用const。我用它来表明不应该改变引用或指针的目标。如你所知,在其他类别的成员上使用它是一件很麻烦的事。
使用const的最佳位置是函数参数,各种指针和引用,常量整数和临时便利值。
临时便利变量的一个例子是:
char buf[256];
char * const buf_end = buf + sizeof(buf);
fill_buf(buf, buf_end);
const size_t len = strlen(buf);
那个buf_end
指针永远不应该指向其他地方,所以使它成为常量是一个好主意。与len
相同的想法。如果buf
内的字符串从未在函数的其余部分中发生更改,则其len
也不应更改。如果可以的话,我甚至会在调用buf
后将fill_buf
更改为const,但C / C ++不会让你这样做。
答案 8 :(得分:1)
关键是海报希望在其实施中保护const
,但仍希望对象可分配。该语言不方便地支持这种语义,因为成员的constness位于相同的逻辑级别并且与可赋值性紧密耦合。
然而,带有引用计数实现或智能指针的pImpl
成语将完全符合海报的要求,因为可转移性随后被移出实现并向更高级别的对象上升。实现对象仅被构造/破坏,从而在较低级别永远不需要赋值。
答案 9 :(得分:0)
我认为你的陈述
如果一个类有const任何成员,那么 编译生成的赋值运算符 被禁用。
可能不对。我有使用const方法的类
bool is_error(void) const;
....
virtual std::string info(void) const;
....
也用于STL。那么您的观察可能依赖于编译器还是仅适用于成员变量?
答案 10 :(得分:0)
这不是太难。制作自己的赋值运算符不会有任何问题。不需要分配const位(因为它们是const)。
<强>更新强>
关于const的含义存在一些误解。这意味着它永远不会改变。
如果一个赋值应该改变它,那么它不是const。 如果您只是想阻止其他人更改它,请将其设为私有,并且不提供更新方法 结束更新
class CTheta
{
public:
CTheta(int nVal)
: m_nVal(nVal), m_pi(3.142)
{
}
double GetPi() const { return m_pi; }
int GetVal() const { return m_nVal; }
CTheta &operator =(const CTheta &x)
{
if (this != &x)
{
m_nVal = x.GetVal();
}
return *this;
}
private:
int m_nVal;
const double m_pi;
};
bool operator < (const CTheta &lhs, const CTheta &rhs)
{
return lhs.GetVal() < rhs.GetVal();
}
int main()
{
std::vector<CTheta> v;
const size_t nMax(12);
for (size_t i=0; i<nMax; i++)
{
v.push_back(CTheta(::rand()));
}
std::sort(v.begin(), v.end());
std::vector<CTheta>::const_iterator itr;
for (itr=v.begin(); itr!=v.end(); ++itr)
{
std::cout << itr->GetVal() << " " << itr->GetPi() << std::endl;
}
return 0;
}
答案 11 :(得分:0)
如果类本身是不可复制的,我只会使用const成员。我用boost :: noncopyable
声明了很多类class Foo : public boost::noncopyable {
const int x;
const int y;
}
然而,如果你想要非常偷偷摸摸并且给自己带来很多潜力 你可以在没有作业的情况下影响复制结构的问题 要小心点。
#include <new>
#include <iostream>
struct Foo {
Foo(int x):x(x){}
const int x;
friend std::ostream & operator << (std::ostream & os, Foo const & f ){
os << f.x;
return os;
}
};
int main(int, char * a[]){
Foo foo(1);
Foo bar(2);
std::cout << foo << std::endl;
std::cout << bar<< std::endl;
new(&bar)Foo(foo);
std::cout << foo << std::endl;
std::cout << bar << std::endl;
}
输出
1
2
1
1
已使用placement new运算符将foo复制到bar。
答案 12 :(得分:0)
从哲学上讲,它看起来像安全性能权衡。 Const用于安全。据我所知,容器使用赋值来重用内存,即为了性能。他们可能会使用显式销毁和替换新的替代(并且逻辑上它更正确),但是赋值有更高效的机会。我想,逻辑上冗余的要求是“可分配”(复制可构造就足够了),但是stl容器希望更快更简单。
当然,可以将赋值实现为显式destroy + placement new以避免const_cast hack
答案 13 :(得分:0)
与其声明数据成员 const
,您还可以创建类 const
的公共表面,除了隐式定义的部分使其成为 (semi
){{3 }}。
class Multiply {
public:
Multiply(double coef) : coef(coef) {}
double operator()(double x) const {
return coef*x;
}
private:
double coef;
};
答案 14 :(得分:-1)
你基本上不想把const成员变量放在一个类中。 (同意使用引用作为类的成员。)
Constness真正用于程序的控制流程 - 防止在代码中错误的时间发生变异。因此,不要在类的定义中声明const成员变量,而是在声明类的实例时全部或全部使用它。