Странная недействительность ссылки C ++ по возвращении - PullRequest
3 голосов
/ 30 января 2012

Посмотрите на следующий код. Цель здесь - вернуть ссылку через две функции (от ReferenceProvider::getReference() до getRef() до main()):

#include <tchar.h>
#include <assert.h>
#include <string>

class BaseClass {
public:
  virtual void write() const {
    printf("In base class\n");
  }
};
typedef BaseClass* BaseClassPointer;

class ChildClass : public BaseClass {
public:
  virtual void write() const {
    printf("In child class\n");
  }
};
typedef ChildClass* ChildClassPointer;

//////////////////////////////////////////////////////////////////////////

ChildClass* g_somePointer = new ChildClass();

class ReferenceProvider {
public:
  const BaseClassPointer& getReference() {
    const BaseClassPointer& val = g_somePointer;
    return val;
  }
};

ReferenceProvider g_provider;

const BaseClassPointer& getRef() {
  std::string test;

  const BaseClassPointer& val = g_provider.getReference();
  return val;
}

int _tmain(int argc, _TCHAR* argv[]) {
  BaseClass* child = getRef();

  assert(child == g_somePointer);
  child->write();

  return 0;
}

Теперь, при отладке этого кода (в Visual C ++), разрыв в return val; в getRef() даст вам такой экран:

Breaking a return

Обратите внимание, что значения g_somePointer и val одинаковы. Теперь перешагните через оператор return, и вы увидите такой экран:

Breaking after return

Обратите внимание, как val стал недействительным (0xcccccccc). Вероятно, это связано с тем, что стек getRef() очищен, а val больше не доступен.

Проблема в том, что child в _tmain() получит это недопустимое значение (0xcccccccc), что сделает child непригодным для использования. Итак, мой первый (и главный) вопрос: Как это сделать правильно?

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

Что делает все это очень странным (и трудным для отладки), так это то, что функция getRef() работает при некоторых условиях:

  1. Если вы измените тип g_somePointer на BaseClass*ChildClass*)
  2. Если вы удалите локальную переменную в getRef() (то есть строка std::string test;)

В обоих случаях ссылочная переменная valgetRef()) не станет недействительной, и функция вернет правильный адрес указателя. Кто-нибудь может мне это объяснить?

Ответы [ 6 ]

6 голосов
/ 30 января 2012

Проблема здесь:

const BaseClassPointer& val = g_somePointer;

Поскольку g_somePointer имеет другой тип (ChildClass* может быть преобразован в BaseClass*, но это не тот же тип), val не может напрямую ссылаться на g_somePointer. Вместо этого создается временная копия, которая преобразуется в правильный тип, и val указывает на это.

Временное действие длится до тех пор, пока val выходит из области видимости в конце функции, поэтому функция возвращает недопустимую ссылку.

Если вы измените тип g_somePointer на BaseClass * (из ChildClass *)

В этом случае преобразование указателя не требуется, поэтому val может напрямую ссылаться на g_somePointer. Код тогда правильный, но хрупкий.

Если вы удалите локальную переменную в getRef () (т.е. строка std :: string test;)

С помощью строковой переменной в конце функции вызывается деструктор, который перезаписывает несуществующий фрейм стека, содержащий временный указатель. Без этого ничто не перезаписывает память, поэтому кажется, что код работает - и это прискорбно, так как делает ошибку намного труднее заметить.

3 голосов
/ 30 января 2012

Чтобы объяснить, что происходит:

const BaseClassPointer& val = g_somePointer;

Эта строка - проблема. Давайте покончим с typedef:

BaseClass* const& val = g_somePointer;

Здесь тип g_somePointer равен ChildClass*. Чтобы присвоить его BaseClass*, необходимо преобразование. Из этого преобразования вводится временный указатель. Этот указатель привязан к ссылке на const, которая продлевает время жизни временных файлов до тех пор, пока ссылка не умрет , что в точности соответствует вашему выражению return val;. В этот момент временный указатель базового класса больше не существует, и у вас неопределенное поведение.

Чтобы избежать всего этого беспорядка, просто верните BaseClass*.

3 голосов
/ 30 января 2012

Вы никогда не сможете вернуть ссылку на локальный объект: он всегда будет выходить за рамки при выходе из функции. Иногда это может выглядеть так, как будто это работает, но это только потому, что данные обычно не изменяются при корректировке указателя стека.

1 голос
/ 30 января 2012

Если вы хотите, чтобы "val" "выжил", метод getReference () должен вернуть ссылку на статический объект.Это «статика», которая будет работать в вашей текущей архитектуре - это другой вопрос.

1 голос
/ 30 января 2012

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

0 голосов
/ 30 января 2012

Вы возвращаете константную ссылку на локальный val вместо возвращенного getRef().

Кроме того, как вы преобразуете указатель на ссылку?

const BaseClassPointer& val = g_somePointer;
return val;

не будет работать, если g_somePointer - указатель - вы использовали *g_somePointer или подобное?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...