简体   繁体   English

C ++:STL使用const类成员时遇到麻烦

[英]C++: STL troubles with const class members

It is an open ended question. 这是一个开放式的问题。 Effective C++. 有效的C ++。 Item 3. Use const whenever possible. 第3项。 尽可能使用const。 Really? 真?

I would like to make anything which doesn't change during the objects lifetime const. 我想做一些在对象生命周期const期间不会改变的东西。 But const comes with it own troubles. 但const带来了它自己的麻烦。 If a class has any const member, the compiler generated assignment operator is disabled. 如果类具有任何const成员,则禁用编译器生成的赋值运算符。 Without an assignment operator a class won't work with STL. 如果没有赋值运算符,则类将无法与STL一起使用。 If you want to provide your own assignment operator, const_cast is required. 如果要提供自己的赋值运算符,则需要const_cast That means more hustle and more room for error. 这意味着更多的喧嚣和更多的错误空间。 How often you use const class members? 你经常使用const类成员?

EDIT: As a rule, I strive for const correctness because I do a lot of multithreading. 编辑:作为一项规则,我努力保持const的正确性,因为我做了很多多线程。 I rarely need to implemented copy control for my classes and never code delete (unless it is absolutely necessary). 我很少需要为我的类实现复制控制,从不编写删除代码(除非绝对必要)。 I feel that the current state of affairs with const contradicts my coding style. 我觉得const的当前状态与我的编码风格相矛盾。 Const forces me to implement assignment operator even though I don't need one. Const迫使我实现赋值运算符,即使我不需要它。 Even without const_cast assignment is a hassle. 即使没有const_cast分配也很麻烦。 You need to make sure that all const members compare equal and then manually copy all non-const member. 您需要确保所有const成员比较相等,然后手动复制所有非const成员。

Code. 码。 Hope it will clarify what I mean. 希望它能澄清我的意思。 The class you see below won't work with STL. 您在下面看到的课程不适用于STL。 You need to implement an assignment for it, even though you don't need one. 您需要为它实现一个赋值,即使您不需要它。

class Multiply {
public:
    Multiply(double coef) : coef_(coef) {}
    double operator()(double x) const {
        return coef_*x;
    }
private:
    const double coef_;
};

You said yourself that you make const "anything which doesn't change during the objects lifetime". 你自己说过你使const“在物体生命期间不会改变的任何东西”。 Yet you complain about the implicitly declared assignment operator getting disabled. 然而,您抱怨隐式声明的赋值运算符被禁用。 But implicitly declared assignment operator does change the contents of the member in question! 但隐式声明的赋值运算符确实改变了有问题的成员的内容! It is perfectly logical (according to your own logic) that it is getting disabled. 完全合乎逻辑(根据您自己的逻辑),它正在被禁用。 Either that, or you shouldn't be declaring that member const. 要么是,要么你不应该声明成员const。

Also, providing you own assignment operator does not require a const_cast . 此外,提供自己的赋值运算符不需要const_cast Why? 为什么? Are you trying to assign to the member you declared const inside your assignment operator? 您是否尝试在赋值运算符中指定您声明为const的成员? If so, why did you declare it const then? 如果是这样,为什么你声明它为const呢?

In other words, provide a more meaningful description of the problems you are running into. 换句话说,提供您正在遇到的问题的更有意义的描述。 The one you provided so far is self-contradictory in the most obvious manner. 到目前为止你提供的那个以最明显的方式是自相矛盾的。

I very rarely use them - the hassle is too great. 我很少使用它们 - 麻烦太大了。 Of course I always strive for const correctness when it comes to member functions, parameters or return types. 当然,在成员函数,参数或返回类型方面,我总是力求const正确性。

As AndreyT pointed out, under these circumstances assignment (mostly) doesn't make a lot of sense. 正如AndreyT指出的那样,在这些情况下,分配(大多数)并没有多大意义。 The problem is that vector (for one example) is kind of an exception to that rule. 问题是vector (例如)是该规则的一个例外。

Logically, you copy an object into the vector , and sometime later you get back another copy of the original object. 从逻辑上讲,您将对象复制到vector ,稍后您将获得原始对象的另一个副本。 From a purely logical viewpoint, there's no assignment involved. 从纯粹的逻辑观点来看,不涉及任务。 The problem is that vector requires that the object be assignable anyway (actually, all C++ containers do). 问题是vector要求对象无论如何都是可分配的(实际上,所有C ++容器都可以)。 It's basically making an implementation detail (that somewhere in its code, it might assign the objects instead of copying them) part of the interface. 它基本上是一个实现细节(在代码中的某个地方,它可能分配对象而不是复制它们)的一部分接口。

There is no simple cure for this. 对此没有简单的治疗方法。 Even defining your own assignment operator and using const_cast doesn't really fix the problem. 即使定义自己的赋值运算符并使用const_cast也无法解决问题。 It's perfectly safe to use const_cast when you get a const pointer or reference to an object that you know isn't actually defined to be const . 当你获得一个const指针或对一个你知道实际上没有被定义为const的对象的引用时,使用const_cast是完全安全的。 In this case, however, the variable itself is defined to be const -- attempting to cast away the const ness and assign to it gives undefined behavior. 但是,在这种情况下,变量本身定义为const - 试图抛弃const并赋予它给出未定义的行为。 In reality, it'll almost always work anyway (as long as it's not static const with an initializer that's known at compile time), but there's no guarantee of it. 实际上,它几乎总是可以工作(只要它不是带有在编译时已知的初始化器的static const ),但是不能保证它。

C++ 11 and newer add a few new twists to this situation. C ++ 11和更新版本为这种情况添加了一些新的曲折。 In particular, objects no longer need to be assignable to be stored in a vector (or other collections). 特别是,不再需要将对象分配以存储在向量(或其他集合)中。 It's sufficient that they be movable. 它们可以移动就足够了。 That doesn't help in this particular case (it's no easier to move a const object than it is to assign it) but does make life substantially easier in some other cases (ie, there are certainly types that are movable but not assignable/copyable). 这在这种特殊情况下没有帮助(移动const对象比分配它更容易)但是在其他一些情况下确实使生活变得更加容易(例如,肯定有可移动但不可分配/可复制的类型)。

In this case, you could use a move rather than a copy by adding a level of indirection. 在这种情况下,您可以通过添加间接级别使用移动而不是副本。 If your create an "outer" and an "inner" object, with the const member in the inner object, and the outer object just containing a pointer to the inner: 如果你创建一个“外部”和一个“内部”对象,内部对象中的const成员和外部对象只包含指向内部的指针:

struct outer { 
    struct inner {
        const double coeff;
    };

    inner *i;
};

...then when we create an instance of outer , we define an inner object to hold the const data. ...然后当我们创建一个outer实例时,我们定义一个inner对象来保存const数据。 When we need to do an assignment, we do a typical move assignment: copy the pointer from the old object to the new one, and (probably) set the pointer in the old object to a nullptr, so when it's destroyed, it won't try to destroy the inner object. 当我们需要做一个赋值时,我们做一个典型的移动赋值:将指针从旧对象复制到新对象,并且(可能)将旧对象中的指针设置为nullptr,所以当它被销毁时,它就赢了试着破坏内部物体。

If you wanted to badly enough, you could use (sort of) the same technique in older versions of C++. 如果你想要足够严重,你可以在旧版本的C ++中使用(类似)相同的技术。 You'd still use the outer/inner classes, but each assignment would allocate a whole new inner object, or you'd use something like a shared_ptr to let the outer instances share access to a single inner object, and clean it up when the last outer object is destroyed. 您仍然使用外部/内部类,但每个赋值将分配一个全新的内部对象,或者您使用类似shared_ptr的东西让外部实例共享对单个内部对象的访问权限,并在最后一个外部物体被摧毁

It doesn't make any real difference, but at least for the assignment used in managing a vector, you'd only have two references to an inner while the vector was resizing itself (resizing is why a vector requires assignable to start with). 它没有任何真正的区别,但至少对于管理向量时使用的赋值,在vector调整自身时,你只有两个对inner引用(调整大小是为什么向量需要赋值可以开始)。

Errors at compile time are painful, but errors at runtime are deadly. 编译时的错误很痛苦,但运行时的错误是致命的。 Constructions using const might be a hassle to code, but it might help you find bugs before you implement them. 使用const的构造可能是代码的麻烦,但它可能会帮助您在实现它们之前找到错误。 I use consts whenever possible. 我尽可能使用consts。

I try my best to follow the advice of using const whenever possible, however I agree that when it comes to class members, const is a big hassle. 我尽可能地遵循使用const的建议,但我同意,当涉及到班级成员时, const是一个很大的麻烦。

I have found that I am very careful with const -correctness when it comes to parameters, but not as much with class members. 我发现在参数方面我非常小心const -correctness,但对于类成员却没有那么多。 Indeed, when I make class members const and it results in an error (due to using STL containers), the first thing I do is remove the const . 实际上,当我使类成员const并且它导致错误时(由于使用STL容器),我做的第一件事就是删除const

I'm wondering about your case... Everything below is but supposition because you did not provide the example code describing your problem, so... 我想知道你的情况......以下所有内容都是假设,因为你没有提供描述你问题的示例代码,所以...

The cause 原因

I guess you have something like: 我想你有类似的东西:

struct MyValue
{
   int         i ;
   const int   k ;
} ;

IIRC, the default assignment operator will do a member-by-member assignment, which is akin to : IIRC,默认赋值运算符将执行逐个成员的赋值,类似于:

MyValue & operator = (const MyValue & rhs)
{
   this->i = rhs.i ;
   this->k = rhs.k ; // THIS WON'T WORK BECAUSE K IS CONST
   return *this ;
} ;

Thus, this won't get generated. 因此,这不会产生。

So, your problem is that without this assignment operator, the STL containers won't accept your object. 所以,你的问题是没有这个赋值运算符,STL容器将不接受你的对象。

As far I as see it: 就我所见:

  1. The compiler is right to not generate this operator = 编译器是不正确生成此operator =
  2. You should provide your own, because only you know exactly what you want 你应该提供自己的,因为只有你知道你想要什么

You solution 你解决方案

I'm afraid to understand what do you mean by const_cast . 我害怕明白const_cast是什么意思。

My own solution to your problem would be to write the following user defined operator : 我自己的问题解决方案是编写以下用户定义的运算符:

MyValue & operator = (const MyValue & rhs)
{
   this->i = rhs.i ;
   // DON'T COPY K. K IS CONST, SO IT SHOULD NO BE MODIFIED.
   return *this ;
} ;

This way, if you'll have: 这样,如果你有:

MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 2 } 

As far as I see it, it is coherent. 据我所知,它是连贯的。 But I guess, reading the const_cast solution, that you want to have something more like: 但我想,阅读const_cast解决方案,你想要更像的东西:

MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 20 } :  K WAS COPIED

Which means the following code for operator = : 这意味着operator =的以下代码:

MyValue & operator = (const MyValue & rhs)
{
   this->i = rhs.i ;
   const_cast<int &>(this->k) = rhs.k ;
   return *this ;
} ;

But, then, you wrote in your question: 但是,那么,你在你的问题中写道:

I would like to make anything which doesn't change during the objects lifetime const 我想做一些在对象生命周期const期间不会改变的东西

With what I supposed is your own const_cast solution, k changed during the object lifetime, which means that you contradict yourself because you need a member variable that doesn't change during the object lifetime unless you want it to change ! 我假设你自己的const_cast解决方案,k在对象生命周期中发生了变化,这意味着你自相矛盾,因为你需要一个在对象生命周期内不会改变的成员变量, 除非你想要它改变

The solution 解决方案

Accept the fact your member variable will change during the lifetime of its owner object, and remove the const. 接受您的成员变量在其所有者对象的生命周期内将更改的事实,并删除co​​nst。

you can store shared_ptr to your const objects in STL containers if you'd like to retain const members. 如果您想保留const成员,可以将shared_ptr存储到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;
}

though, as others have pointed out it's somewhat of a hassle and often easier in my opinion to remove the const qualified members if you desire the compiler generated copy constructor. 但是,正如其他人已经指出的那样,如果你想要编译器生成的复制构造函数,那么在我看来删除const限定成员往往更容易。

I only use const on reference or pointer class members. 我只在引用或指针类成员上使用const。 I use it to indicate that the target of the reference or pointer should not be changed. 我用它来表明不应该改变引用或指针的目标。 Using it on other kinds of class members is a big hassle as you found out. 你发现,在其他类别的成员上使用它是一个很大的麻烦。

The best places to use const is in function parameters, pointers and references of all kinds, constant integers and temporary convenience values. 使用const的最佳位置是函数参数,指针和各种引用,常量整数和临时便利值。

An example of a temporary convenience variable would be: 临时便利变量的一个例子是:

char buf[256];
char * const buf_end = buf + sizeof(buf);
fill_buf(buf, buf_end);
const size_t len = strlen(buf);

That buf_end pointer should never point anywhere else so making it const is a good idea. 那个buf_end指针永远不应该指向其他任何地方,所以使它成为const是一个好主意。 The same idea with len . len相同的想法。 If the string inside buf never changes in the rest of the function then its len should not change either. 如果buf的字符串在函数的其余部分中永远不会改变,那么它的len也不应该改变。 If I could, I would even change buf to const after calling fill_buf , but C/C++ does not let you do that. 如果可以,我甚至会在调用fill_buf之后将buf更改为const,但C / C ++不会让你这样做。

The point is that the poster wants const protection within his implementation but still wants the object assignable. 关键是海报希望在他的实现中保持const但仍然希望对象可分配。 The language does not support such semantics conveniently as constness of the member resides at the same logical level and is tightly coupled with assignability. 该语言不方便地支持这种语义,因为成员的constness位于相同的逻辑级别并且与可赋值性紧密耦合。

However, the pImpl idiom with a reference counted implementation or smart pointer will do exactly what the poster wants as assignability is then moved out of the implementation and up a level to the higher level object. 然而,具有引用计数实现或智能指针的pImpl惯用语将完全符合海报的要求,因为可分配性随后被移出实现并向更高级别对象上升。 The implementation object is only constructed/destructed whence assignment is never needed at the lower level. 实现对象仅被构造/破坏,从而在较低级别永远不需要赋值。

I think your statement 我想你的说法

If a class has const any member, the compiler generated assignment operator is disabled. 如果类具有const任何成员,则禁用编译器生成的赋值运算符。

Might be incorrect. 可能不对。 I have classes that have const method 我有使用const方法的类

bool is_error(void) const;
....
virtual std::string info(void) const;
....

that are also used with STLs. 也用于STL。 So perhaps your observation is compiler dependent or only applicable to the member variables? 那么您的观察结果可能依赖于编译器还是仅适用于成员变量?

It isn't too hard. 这不是太难。 You shouldn't have any trouble making your own assignment operator. 制作自己的赋值运算符不会有任何问题。 The const bits don't need to be assigned (as they're const). 不需要分配const位(因为它们是const)。

Update 更新
There is some misunderstanding about what const means. 关于const的含义存在一些误解。 It means that it will not change, ever. 这意味着它永远不会改变。

If an assignment is supposed to change it, then it isn't const. 如果一个赋值应该改变它,那么它不是const。 If you just want to prevent others changing it, make it private and don't provide an update method. 如果您只是想阻止其他人更改它,请将其设为私有,并且不提供更新方法。
End Update 结束更新

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

I would only use const member iff the class itself is non-copyable. 我只会使用const成员iff类本身是不可复制的。 I have many classes that I declare with boost::noncopyable 我用boost :: noncopyable声明了很多类

class Foo : public boost::noncopyable {
    const int x;
    const int y;
}

However if you want to be very sneaky and cause yourself lots of potential problems you can effect a copy construct without an assignment but you have to be a bit careful. 但是,如果你想要非常偷偷摸摸并且给自己带来很多潜在的问题,你可以在没有任务的情况下实现复制结构,但你必须要小心一点。

#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;

}

outputs 输出

1
2
1
1

foo has been copied to bar using the placement new operator. 已使用placement new运算符将foo复制到bar。

Philosophically speaking, it looks as safety-performance tradeoff. 从哲学上讲,它看起来像安全性能权衡。 Const used for safety. Const用于安全。 As I understand, containers use assignment to reuse memory, ie for sake of performance. 据我所知,容器使用赋值来重用内存,即为了性能。 They would may use explicit destruction and placement new instead (and logicaly it is more correct), but assignment has a chance to be more efficient. 他们可能会使用显式销毁和替换新的替代(并且逻辑上它更正确),但是赋值有更高效的机会。 I suppose, it is logically redundant requirement "to be assignable" (copy constructable is enough), but stl containers want to be faster and simpler. 我认为,逻辑冗余要求“可分配”(复制可构造就足够了),但是stl容器希望更快更简单。

Of course, it is possible to implement assignment as explicit destruction+placement new to avoid const_cast hack 当然,可以将赋值实现为显式destroy + placement new以避免const_cast hack

You basically never want to put a const member variable in a class. 你基本上不想把一个const成员变量放在一个类中。 (Ditto with using references as members of a class.) (同上使用引用作为类的成员。)

Constness is really intended for your program's control flow -- to prevent mutating objects at the wrong times in your code. Constness真正用于程序的控制流程 - 防止在代码中错误的时间发生变异。 So don't declare const member variables in your class's definition, rather make it all or nothing when you declare instances of the class. 因此,不要在类的定义中声明const成员变量,而是在声明类的实例时全部或全部使用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM