Как передать ссылки по значению в C++?

Я пытаюсь создать неизменный тип (класс) в C++,

Я сделал это так, чтобы все методы "aka member functions" не модифицировали объект и вместо этого возвращали новый экземпляр.

Я сталкиваюсь с множеством проблем, но все они вращаются вокруг ссылочных типов в 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.

ОБНОВИТЬ

Спасибо за ответы, я понимаю, что нет указателей. (см. мой другой вопрос, который действительно является продолжением этого)

6 ответов

Решение

В 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, примененный к ссылке, делает константу указанного объекта постоянной, а не ссылку. Ссылки всегда постоянны.

Разве назначение по определению не является постоянной операцией?

Похоже, вы пытаетесь присвоить что-то константной ссылке, что полностью опровергает идею константной ссылки.

Я думаю, что вы можете искать указатель вместо ссылки.

Это не работает так в C++.

Когда вы передаете ссылку на объект, вы фактически передаете адрес в памяти объекта. Ссылки не могут быть повторно размещены на других объектах, следовательно, пословица C++ "ссылка - это объект". Вы должны сделать копию, чтобы изменить ее. Java сделает это за кулисами для вас. C++, вам просто нужно скопировать его.

Разве вы не забыли установить вызываемый метод как const?

РЕДАКТИРОВАТЬ: так, с фиксированной константой.

Может быть, вы должны сделать что-то вроде

Imm & tmp = p_im.someOtherOp();

Затем выполните дальнейшую операцию с переменной tmp.

Если вы установите переменную или параметр как const &, вы просто не сможете назначить их.

В C++ есть нечто лучшее, чем неизменяемые типы -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, хотя функция может тогда, конечно, бросать std::bad_alloc.

Отметьте это, чтобы узнать о временном сроке жизни 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();
}

Вам нужно сделать новую копию вашего входящего аргумента. Вы можете делать то, что хотите, несколькими эквивалентными способами: 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)
}

обе формы эквивалентны.

Другие вопросы по тегам