允许成员为const,同时仍然在类上支持operator =

时间:2010-04-12 06:50:06

标签: c++ operators const

我班上有几个成员const,因此只能通过初始化列表进行初始化,如下所示:

class MyItemT
{
public:
    MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo)
        : mMyPacket(aMyPacket),
          mMyInfo(aMyInfo)
    {
    }

private:
    const MyPacketT mMyPacket;
    const MyInfoT mMyInfo;
};

我的类可以在我们的一些内部定义的容器类(例如向量)中使用,并且这些容器要求在类中定义operator=

当然,我的operator=需要做这样的事情:

MyItemT&
MyItemT::operator=(const MyItemT& other)
{
    mMyPacket = other.mPacket;
    mMyInfo = other.mMyInfo;
    return *this;
}

这当然不起作用,因为mMyPacketmMyInfoconst成员。

除了让这些成员不是const(我不想做)之外,还有任何关于如何解决这个问题的想法?

5 个答案:

答案 0 :(得分:7)

如果你有一个可以在构造完成后更改它们的赋值运算符,那么你违反了const的定义。如果你真的需要,我认为Potatoswatter的放置新方法可能是最好的,但是如果你有一个赋值运算符,你的变量实际上不是常量,因为有人可以创建一个新实例并用它来改变他们的价值观

答案 1 :(得分:3)

您可以存储指针(或智能指针),而不是直接在容器中存储对象。这样,您就不必改变您班级中的任何成员 - 您获得与传入的完全相同的对象,const和所有成员。

当然,这样做可能会在一定程度上改变应用程序的内存管理,这可能是一个不想要的理由。

答案 2 :(得分:2)

这是一个肮脏的黑客,但你可以摧毁和重建自己:

MyItemT&
MyItemT::operator=(const MyItemT& other)
{
    if ( this == &other ) return *this; // "suggested" by Herb Sutter ;v)

    this->MyItemT::~MyItemT();
    try {
        new( this ) MyItemT( other );
    } catch ( ... ) {
        new( this ) MyItemT(); // nothrow
        throw;
    }
    return *this;
}

编辑:以免破坏我的可信度,我实际上并不这样做,我会删除const。但是,我一直在讨论改变这种做法,因为const只是有用而且最好尽可能使用。

有时资源和对象表示的值之间存在区别。只要资源相同,成员就可以通过更改值来构造const,并且在此时获得编译时安全性会很好。

编辑2: @Charles Bailey提供了这个精彩(且非常关键)的链接:http://gotw.ca/gotw/023.htm

  • 任何派生类operator=中的语义都很棘手。
  • 它可能效率低下,因为它不会调用已定义的赋值运算符。
  • 它与不稳定的operator&重载(无论如何)
  • 不兼容

编辑3:通过“哪个资源”与“什么样的价值”区分进行思考,似乎operator=应该始终更改值而不是资源。然后,资源标识符可以是const。在示例中,所有成员都是const。如果“信息”是存储在“数据包”中的内容,那么数据包可能是const而信息不是。

因此,如果“信息”实际上是元数据,那么问题不在于将分配的语义理解为在此示例中缺少明显的值。如果任何类拥有MyItemT想要将其从一个数据包切换到另一个数据包,它需要放弃并使用auto_ptr<MyItemT>代替,或者采取与上述类似的黑客攻击(身份测试是不必要的)但剩下的catch已从外部实施 。但是operator=不应该改变资源绑定,除非作为一个绝对不会干扰其他任何内容的特殊功能。

请注意,此约定适用于Sutter关于在分配方面实施复制构造的建议。

 MyItemT::MyItemT( MyItemT const &in )
     : mMyPacket( in.mMyPacket ) // initialize resource, const member
     { *this = in; } // assign value, non-const, via sole assignment method

答案 3 :(得分:1)

您可以考虑将MyPacketTMyInfoT成员指向const(或指向const的智能指针)。这样,数据本身仍然标记为const和immutable,但是如果有意义的话,你可以干净地“交换”到赋值中的另一组const数据。实际上,您可以使用交换习惯用法以异常安全的方式执行赋值。

因此,您可以获得const的好处,以帮助您防止意外地允许您希望设计阻止的更改,但您仍然允许从另一个对象分配整个对象。例如,这将允许您在STL容器中使用此类的对象。

您可能会将此视为'pimpl'习语的特殊应用。

有些事情:

#include <algorithm>    // for std::swap

#include "boost/scoped_ptr.hpp"

using namespace boost;

class MyPacketT {};
class MyInfoT {};


class MyItemT
{
public:
    MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo)
        : pMyPacket(new MyPacketT( aMyPacket)),
          pMyInfo(new MyInfoT( aMyInfo))
    {
    }

    MyItemT( MyItemT const& other)
        : pMyPacket(new MyPacketT( *(other.pMyPacket))),
          pMyInfo(new MyInfoT( *(other.pMyInfo)))
    {   

    }

    void swap( MyItemT& other) 
    {
        pMyPacket.swap( other.pMyPacket);
        pMyInfo.swap( other.pMyInfo);        
    }


    MyItemT const& operator=( MyItemT const& rhs)
    {
        MyItemT tmp( rhs);

        swap( tmp);

        return *this;
    }

private:
    scoped_ptr<MyPacketT const> pMyPacket;
    scoped_ptr<MyInfoT const> pMyInfo;
};

最后,我将我的示例更改为使用scoped_ptr<>而不是shared_ptr<>,因为我认为这是OP意图的更一般的表示。但是,如果可以共享“可重新分配的”const成员(并且这可能是真的,因为我理解OP为什么需要他们const),那么使用shared_ptr<>可能是一种优化让shared_ptr<>类的复制和赋值操作处理这些对象的事情 - 如果你没有其他成员需要特殊的复制或分配语义,那么你的课程就简单了很多,并且您甚至可以通过共享MyPacketTMyInfoT对象的副本来节省大量内存使用量。

答案 4 :(得分:1)

我认为你可以使用特殊的const代理。

template <class T>
class Const
{
public:
  // Optimal way of returning, by value for built-in and by const& for user types
  typedef boost::call_traits<T>::const_reference const_reference;
  typedef boost::call_traits<T>::param_type param_type;

  Const(): mData() {}
  Const(param_type data): mData(data) {}
  Const(const Const& rhs): mData(rhs.mData) {}

  operator const_reference() const { return mData; }

  void reset(param_type data) { mData = data; } // explicit

private:
  Const& operator=(const Const&); // deactivated
  T mData;
};

现在,您将拥有const MyPacketT而不是Const<MyPacketT>。并不是说接口只提供了一种改变它的方法:通过显式调用reset

我认为mMyPacket.reset的任何使用都可以轻松搜索。正如@MSalters所说,它可以防止墨菲,而不是马基雅维利:)