允许成员为常量,同时仍然支持类上的operator=

发布于 2024-08-28 07:52:04 字数 718 浏览 9 评论 0原文

我的类中有几个成员是 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(我不想这样做)之外,还有关于如何解决此问题的任何想法吗?

I have several members in my class which are const and can therefore only be initialised via the initialiser list like so:

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

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

My class can be used in some of our internally defined container classes (e.g. vectors), and these containers require that operator= is defined in the class.

Of course, my operator= needs to do something like this:

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

which of course doesn't work because mMyPacket and mMyInfo are const members.

Other than making these members non-const (which I don't want to do), any ideas about how I could fix this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

等你爱我 2024-09-04 07:52:04

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

You're kind of violating the definition of const if you have an assignment operator that can change them after construction has finished. If you really need to, I think Potatoswatter's placement new method is probably best, but if you have an assignment operator your variables aren't really const, since someone could just make a new instance and use it to change their values

叹梦 2024-09-04 07:52:04

您可以存储指针(或智能指针),而不是直接在容器中存储对象。这样,您不必更改类的任何成员 - 您将返回与传入的对象完全相同的对象、const 等。

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

Rather than storing objects in your containers directly, you might be able to store pointers (or smart pointers). That way, you don't have to mutate any of the members of your class -- you get back exactly the same object as you passed in, const and all.

Of course, doing this will probably change the memory management of your application somewhat, which may well be a good enough reason not to want to.

并安 2024-09-04 07:52:04

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

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 代替,或者诉诸类似的方法如上所述的 hack(身份测试是不必要的,但 catch 仍然存在)从外部实现。但是 operator= 不应该更改资源绑定,除非作为绝对不会干扰其他任何内容的额外特殊功能。

请注意,此约定与萨特关于在分配方面实施副本构造的建议很好地配合。

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

It's a dirty hack, but you can destroy and reconstruct yourself:

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

Edit: lest I destroy my credibility, I don't actually do this myself, I would remove the const. However, I've been debating changing the practice, because const simply is useful and better to use wherever possible.

Sometimes there is a distinction between the resource and the value represented by an object. A member may be const through changes to value as long as the resource is the same, and it would be nice to get compile-time safety on that.

Edit 2: @Charles Bailey has provided this wonderful (and highly critical) link: http://gotw.ca/gotw/023.htm.

  • Semantics are tricky in any derived class operator=.
  • It may be inefficient because it doesn't invoke assignment operators that have been defined.
  • It's incompatible with wonky operator& overloads (whatever)
  • etc.

Edit 3: Thinking through the "which resource" vs "what value" distinction, it seems clear that operator= should always change the value and not the resource. The resource identifier may then be const. In the example, all the members are const. If the "info" is what's stored inside the "packet," then maybe the packet should be const and the info not.

So the problem isn't so much figuring out the semantics of assignment as lack of an obvious value in this example, if the "info" is actually metadata. If whatever class owns a MyItemT wants to switch it from one packet to another, it needs to either give up and use an auto_ptr<MyItemT> instead, or resort to a similar hack as above (the identity test being unnecessary but the catch remaining) implemented from outside. But operator= shouldn't change resource binding except as an extra-special feature which absolutely won't interfere with anything else.

Note that this convention plays well with Sutter's advice to implement copy construction in terms of assignment.

 MyItemT::MyItemT( MyItemT const &in )
     : mMyPacket( in.mMyPacket ) // initialize resource, const member
     { *this = in; } // assign value, non-const, via sole assignment method
凌乱心跳 2024-09-04 07:52:04

我认为你可以使用特殊的 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,而不是 const MyPacketT。并不是说该接口只提供了一种更改它的方法:通过显式调用reset

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

I think you could get away with a special const proxy.

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

Now, instead of const MyPacketT you would have Const<MyPacketT>. Not that the interface only provides one way to change it: through an explicit call to reset.

I think any use of mMyPacket.reset can easily be search for. As @MSalters said it protects against Murphy, not Machiavelli :)

请持续率性 2024-09-04 07:52:04

您可以考虑将 MyPacketTMyInfoT 成员设置为指向 const 的指针(或指向 const 的智能指针)。这样,数据本身仍然被标记为 const 和不可变的,但如果有意义的话,您可以在赋值中干净地“交换”到另一组 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< 的副本来节省大量内存使用量/代码> 对象。

You might consider making the MyPacketT and MyInfoT members be pointers to const (or smart pointers to const). This way the data itself is still marked const and immutable, but you can cleanly 'swap' to another set of const data in an assignment if that makes sense. In fact, you can use the swap idiom to perform the assignment in an exception safe manner.

So you get the benefit of const to help you prevent accidentally allowing changes that you want the design to prevent, but you still allow the object as a whole to be assigned from another object. For example, this will let you use objects of this class in STL containers.

You might look at this as a special application of the 'pimpl' idiom.

Something along the lines of:

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

Finally, I changed my example to use scoped_ptr<> instead of shared_ptr<> because I thought it was a more general representation of what the OP intended. However, if the 'reassignable' const members can be shared (and that's probably true, given my understanding of why the OP wants them const), then it might be an optimization to use shared_ptr<>'s and let the copy and assignment operations of the shared_ptr<> class take care of things for those objects - if you have no other members that require special copy or assign sematics, then your class just got a lot simpler, and you might even save a significant bit of memory usage by being able to share copies of the MyPacketT and MyInfoT objects.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文