2

我正在尝试在 C++ 中创建不可变类型(类),

我这样做是为了让所有方法“又名成员函数”都不会修改对象并返回一个新实例。

我遇到了很多问题,但它们都围绕 C++ 中的引用类型。

一个示例是通过引用传递相同类类型的参数时:

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   p_im = p_im.someOtherOp(); //error, p_im is const, can't modify it!
   ...
}

该错误是由通过引用传递值引起的。相反,如果我是按值传递引用,那么上面的错误行就不会是错误!

考虑一个 Java/C# 示例

class Imm
{
    ...
    Imm someOp( Imm p_im )
    {
        ....
        p_im = p_im.someOtherOp(); //ok, you're not modifying the 
                 //original object, just changing the local reference
        ....  
    }
    ....
}

我怎样才能在 C++ 中做这样的事情?我知道我可以使用指针,但后来我遇到了整个内存管理混乱。我不想担心谁拥有对对象的引用。

理想情况下,我想将类设计为类似于 python 中的不可变字符串;您可以在没有注意到甚至不知道它们是不可变的情况下使用它们,并且它们的行为符合您的预期;他们只是工作。

编辑

当然,我可以通过按值传递或使用临时变量(这是我目前正在做的)来解决它。我要问的是“如何在 C++ 中按值传递引用”

我期待答案围绕 STL 中的某些内容展开,我目前正在研究 smart_ptr 模板系列。

更新

感谢您的回复,我意识到指针无法逃脱。(见我的另一个问题,这真的是对这个问题的跟进)

4

7 回答 7

4

根据定义,赋值不是常量操作吗?

您看起来好像正在尝试将某些内容分配给 const 引用,这完全违背了 const 引用的想法。

我认为您可能正在寻找指针而不是参考。

于 2009-03-16T00:50:09.157 回答
4

在 Java 和 C# 中,您并没有真正处理引用 - 它们更像是句柄或指针。C++ 中的引用实际上是原始对象的另一个名称,而不是指向它的指针(尽管它可以用指针实现)。当您为引用分配值时,您正在分配给对象本身。在初始化引用时可以使用=字符存在混淆,但它是初始化,而不是赋值。

 Imm im, im2, im3; 
 Imm &imr = im;  // initialize a reference to im
 imr = im2; // assign im2 to imr (changes the value of im as well)
 Imm *imp = &im; // initialize a pointer to the address of im
 imp = &im3; // assign the address of im3 to imp (im is unnaffected);
 (*imp) = im2; // assign im2 to imp (modifies im3 as well).

如果您特别想通过“按值引用”,那么您实际上是在要求一个矛盾。根据定义,引用是通过引用传递的。正如在别处指出的那样,您可以按值传递指针,也可以直接传递值。如果你真的想要,你可以在一个类中保留一个引用并通过值传递它:

 struct ImmRef
 {
     Imm &Ref;
     ImmRef(Imm &ref) : Ref(ref) {}
 };

另请注意,应用于引用的 const 使引用的对象成为常量,而不是引用。引用总是常量。

于 2009-03-16T01:30:55.797 回答
3

它在 C++ 中不像那样工作。

当您传递对对象的引用时,实际上是在传递对象内存中的地址。引用也不能重新定位到其他对象,因此 C++ 格言“引用就是对象”。您必须制作副本才能对其进行修改。Java 将在幕后为您完成这项工作。C++,你只需要复制它。

于 2009-03-16T04:24:38.330 回答
1

你不是忘了把你调用的方法设置为const吗?

编辑:所以,在 const 固定的情况下。

也许你应该做类似的事情

Imm & tmp = p_im.someOtherOp();

然后对 tmp 变量做进一步的操作。

如果您将变量或参数设置为 const & 您就不能分配给它。

于 2009-03-16T00:45:47.030 回答
0

您需要制作传入参数的新副本。你可以用几种等效的方式做你想做的事:1)你可以按值传递:

Imm Imm::someOp( Imm im ) const {
   im = im.someOtherOp();      // local im is a copy, original im not modified
   return im;                  // return by value (another copy)
}

或 2)您可以通过引用传递并显式复制:

Imm Imm::someOp( const Imm & im ) const {
   Imm tmp = im.someOtherOp(); // local tmp is a copy
   return tmp;                 // return by value (another copy)
}

两种形式是等价的。

于 2009-03-16T00:58:49.433 回答
0

检查此以了解临时寿命http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   //Imm& im = p_im.someOtherOp();       // will *not* work
   const Imm& im = p_im.someOtherOp();   // will work, but you get a const reference
   ...
}

但是你可以使用 boost::shared_ptr

shared_ptr<Imm> Imm::someOtherOp() const
{
  shared_ptr<Imm> ret = new Imm;
  ...
  return ret;
}

shared_ptr<Imm> Imm::someOp(const share_ptr<Imm>& p_im) const
{
  shared_ptr<Imm> im = p_im->someOtherOp();
}
于 2009-03-16T05:20:56.870 回答
0

C++ 有比不可变类型更好的东西——<code>const。根据您的需要,单一类型可以是可变的或不可变的。也就是说,有一些有用的模式可以处理短寿命(近)副本:

void f(const X &x) {
  // Trivial case: unconditional copy
  X x2=transform(x);
  // Less trivial: conditional copy
  std::optional<X> maybe;
  const X &use=need_copy ? maybe.emplace(transform(x)) : x;
  use.go();  // whichever, x or *maybe
}  // *maybe destroyed iff created, then x2 destroyed

std::unique_ptr可以在 C++17 之前以类似的方式使用,尽管该函数当然可以 throw std::bad_alloc

于 2019-07-24T00:27:11.480 回答