Должен ли я использовать указатели для полей моего класса C ++?

После прочтения вопроса о различиях между указателями и ссылками, Я решил, что хочу использовать ссылки вместо указателей для полей моего класса. Однако кажется, что это невозможно, потому что они не могут быть объявлены неинициализированными (верно?).

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

В моем фрагменте bar1 автоматически создается с помощью конструктора по умолчанию (что не то, что я хочу), & bar2 вызывает ошибку компилятора, потому что вы не можете использовать неинициализированные ссылки (правильно?), А * bar3 доволен как larry, потому что указатели могут быть объявленным неинициализированным (кстати, лучше ли установить это значение в NULL?).

class Foo
{
public:
    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

Похоже, в этом сценарии мне нужно использовать указатели, это правда? Кроме того, как лучше всего использовать переменную? Синтаксис -> немного громоздок ... Не повезло? А как насчет интеллектуальных указателей и т. Д.? Это актуально?

Обновление 1:

Почему после попытки реализовать поле ссылочной переменной в моем классе и его инициализации в конструкторе может появиться следующая ошибка?

../src/textures/VTexture.cpp: In constructor ‘vimrid::textures::VTexture::VTexture()’:
../src/textures/VTexture.cpp:19: error: uninitialized reference member ‘vimrid::textures::VTexture::image’

Вот настоящий код:

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage);
private:
    vimrid::imaging::ImageMatrix ℑ
}

// VTexture.cpp
VTexture::VTexture(ImageMatrix &rImage)
    : image(rImage)
{
}

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

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rimage) : image(rImage) { }
}

Обновление 2:

Фред Ларсон - Да! Есть конструктор по умолчанию; Я пренебрегал этим, потому что думал, что это не имеет отношения к проблеме (как глупо с моей стороны). После удаления конструктора по умолчанию я вызвал ошибку компилятора, потому что класс используется с std :: vector, который требует наличия конструктора по умолчанию. Похоже, я должен использовать конструктор по умолчанию и, следовательно, должен использовать указатель. Позор ... или нет? :)


person Nick Bolton    schedule 10.04.2009    source источник
comment
обычные переменные (кстати, какой для них правильный термин?), вы можете называть их `` переменные стека ''   -  person veefu    schedule 10.04.2009
comment
обычные переменные - ›Примитивные типы данных   -  person Robert    schedule 10.04.2009
comment
примитивные типы данных - не единственные нормальные переменные ... вы также можете иметь определенные пользователем типы данных как обычные переменные.   -  person Shree    schedule 10.04.2009
comment
Хм, когда я говорю «обычные переменные», я имею в виду, а не указатель и не ссылку, поэтому я имею в виду Foo :: bar1 как обычную переменную - это то, о чем вы, ребята, говорите?   -  person Nick Bolton    schedule 10.04.2009
comment
вы называете его членами, указателем или ссылочным членом.   -  person Shree    schedule 10.04.2009
comment
Ах, так в C ++ термин член, а не поле, как в C #?   -  person Nick Bolton    schedule 10.04.2009
comment
хм, вы также можете инициализировать ссылку с помощью конструктора по умолчанию ... Фактически, вы должны.   -  person Evan Teran    schedule 10.04.2009
comment
r3n: термин для использования вами обычных переменных - это объекты-члены, а не указатели на элементы, ссылки на элементы, локальные переменные, переменные стека и т. д.   -  person    schedule 10.04.2009
comment
Проблема с вектором, требующим конструктора по умолчанию, - это специфическая проблема библиотеки, я считаю, и не обязательно стандарт C ++ - если вы не используете методы, требующие конструктора по умолчанию, такого как operator [], тогда он вам не нужен.   -  person Greg Domjan    schedule 10.04.2009
comment
Грег: vector :: op [] не требует ctor по умолчанию. вы думаете о std :: map. struct A {A (int) {}}; int main () {vector ‹A› v; v.push_back (A (5)); v [0];} компилируется.   -  person    schedule 11.04.2009
comment
Я посмотрел на реализацию type_index cass в LLVM. Конструктор использует ссылку на type_info, но внутреннее закрытое поле, в котором он содержится, является указателем на объект type_info. Почему они внутренне используют указатель вместо ссылки? github.com/llvm/llvm-project/blob/ main / libcxx / include / Альтернативный вариант impl, который использует ссылку gist .github.com / apivovarov /   -  person x4444    schedule 18.12.2020


Ответы (6)


Ответ на вопрос 1:

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

Правильно.


Ответ на вопрос 2:

В моем фрагменте bar1 автоматически создается с помощью конструктора по умолчанию (это не то, что я хочу), & bar2 вызывает ошибку компилятора, потому что вы не можете использовать неинициализированные ссылки (правильно?),

Вы инициализируете ссылки вашего класса в списке инициализаторов вашего конструктора:

class Foo
{
public:
    Foo(Bar &rBar) : bar2(rBar), bar3(NULL)
    {
    }

    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

Ответ на вопрос 3:

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

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

class Foo
{
public: 
  Foo()  : x(0), y(4)
  {
  }

  int x, y;
};

Ответ на вопрос 4:

указатели могут быть объявлены неинициализированными (кстати, лучше ли установить это значение в NULL?).

Их можно объявить неинициализированными да. Лучше инициализировать их значением NULL, потому что тогда вы можете проверить, действительны ли они.

int *p = NULL;
//...

//Later in code
if(p)
{
  //Do something with p
}

Ответ на вопрос 5:

Похоже, в этом сценарии мне нужно использовать указатели, это правда? Кроме того, как лучше всего использовать переменную?

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

Указатель имеет свой собственный адрес в памяти, тогда как массив следует рассматривать как разделяющий адрес переменной, на которую он ссылается.

Со ссылкой после ее инициализации и объявления вы используете ее так же, как если бы вы использовали переменную, на которую она ссылается. Особого синтаксиса нет.

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

int x=0;
int *p = &x;//p holds the address of x
int &r(x);//r is a reference to x
//From this point *p == r == x
*p = 3;//change x to 3
r = 4;//change x to 4
//Up until now
int y=0;
p = &y;//p now holds the address of y instead.

Ответ на вопрос 6:

А как насчет умных указателей и т. Д.? Это актуально?

Интеллектуальные указатели (см. Boost :: shared_ptr) используются, чтобы при выделении памяти в куче не нужно было вручную освобождать память. Ни один из приведенных выше примеров не размещен в куче. Вот пример, когда использование умных указателей могло бы помочь.

void createANewFooAndCallOneOfItsMethods(Bar &bar)
{
    Foo *p = new Foo(bar);  
    p->f();
    //The memory for p is never freed here, but if you would have used a smart pointer then it would have been freed here.  
}

Ответ на вопрос 7:

Обновление 1:

Почему после попытки реализовать поле ссылочной переменной в моем классе и его инициализации в конструкторе может появиться следующая ошибка?

Проблема в том, что вы не указали список инициализаторов. См. Мой ответ на вопрос 2 выше. Все после двоеточия:

class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage)
      : image(rImage)
    { 
    }
private:
    vimrid::imaging::ImageMatrix ℑ
}
person Brian R. Bondy    schedule 10.04.2009
comment
@Brian: Спасибо за ответ. У меня проблема с использованием списка инициализаторов участников. Не могли бы вы взглянуть на обновление №1? - person Nick Bolton; 10.04.2009
comment
@ Брайан: Ты уверен? Я показал примеры списка инициализаторов как в файле cpp, так и в файле h ... В любом случае, я добавил обновление 2. Пожалуйста, дайте мне знать, если я все еще где-то ошибаюсь. - person Nick Bolton; 10.04.2009
comment
Я думаю, у вас может быть 2 конструктора - person Brian R. Bondy; 10.04.2009

Их можно инициализировать. Вам просто нужно использовать список инициализаторов членов.

Foo::Foo(...) : bar1(...), bar2(...), bar3(...)
{
  // Whatever
}

Таким образом рекомендуется инициализировать все ваши переменные-члены. В противном случае, для типов, отличных от примитивных, C ++ все равно инициализирует их конструктором по умолчанию. Назначение их в фигурных скобках фактически их переназначает, а не инициализирует.

Также имейте в виду, что список инициализаторов элементов указывает КАК инициализировать переменные-члены, а НЕ ПОРЯДОК. Члены инициализируются в том порядке, в котором они объявлены, а не в порядке инициализаторов.

person Fred Larson    schedule 10.04.2009
comment
Спасибо за ответ. У меня проблема с использованием списка инициализаторов участников. Не могли бы вы взглянуть на обновление №1? - person Nick Bolton; 10.04.2009
comment
Некоторые непримитивные типы по умолчанию не инициализируются. Примерами являются объекты POD и части POD структуры, не относящейся к POD. Для всех неинициализированных элементов присвоение внутри фигурных скобок эквивалентно инициализации. Рекомендуемый способ - это списки инициализации. Я бы добавил +1 за последнюю часть ... - person David Rodríguez - dribeas; 10.04.2009
comment
Из сообщения об ошибке видно, что у вас есть конструктор по умолчанию, который не инициализирует элемент image. - person Fred Larson; 10.04.2009
comment
... что делает интересный момент - если у вас есть конструктор по умолчанию, вам нужно будет найти или создать какой-либо объект для инициализации этой ссылки. Если допустимо, чтобы он ни на что не ссылался, это должен быть указатель. - person Fred Larson; 10.04.2009
comment
@ Фред Ларсон: Очень хорошо замеченный сэр! Я пренебрегал этим, потому что думал, что это не связано ... иронично. - person Nick Bolton; 10.04.2009
comment
Хорошо, если допустимо, чтобы ваша VTexture существовала без изображения, вам понадобится указатель. Если нет, вы можете предоставить объект изображения по умолчанию (возможно, созданный в анонимном пространстве имен) и инициализировать ссылку на него. - person Fred Larson; 10.04.2009

Используйте шаблон проектирования нулевого объекта

Я использую ints, но он будет одинаковым для любого типа.

//header file
class Foo
{
public:
   Foo( void );
   Foo( int& i );
private:
   int& m_int;
};

//source file
static int s_null_Foo_m_i;

Foo::Foo( void ) :
   m_i(s_null_Foo_m_i)
{ }

Foo::Foo( int& i ) :
   m_i(i)
{ }

Теперь вы должны убедиться, что Foo имеет смысл при построении по умолчанию. Вы даже можете определить, когда Foo был сконструирован по умолчанию.

bool Foo::default_constructed( void )
{
   return &m_i == &s_null_Foo_m_i;
}

Я полностью согласен с мнением: всегда предпочитаю ссылки указателям. Есть два примечательных случая, когда вам не обойтись без референтного члена:

  1. Null имеет значимое значение.

    Этого можно избежать с помощью шаблона проектирования нулевого объекта.

  2. Класс должен быть назначаемым.

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

person deft_code    schedule 10.04.2009

Существует также побочный эффект, когда вы определяете, когда вы определяете Bar и Bar *

class Foo
{
public:
    Bar bar1; // Here, you create a dependency on the definition of Bar, so the header //file for bar always needs to be included.
    Bar &bar2;
    Bar *bar3; //Here, you create a pointer, and a forward declaration is enough, you don't have to always include the header files for Bar , which is preferred.
}
person J.W.    schedule 10.04.2009
comment
Спасибо, можем ли мы использовать предварительное объявление со ссылочными элементами или нам нужно включить заголовок? Думаю, это первое ... - person Nick Bolton; 10.04.2009

Использование ссылок только потому, что синтаксис -> громоздок, не лучшая причина ... Ссылки имеют одно большое преимущество перед указателями в том, что значения NULL невозможны без обмана, но также имеют недостатки в инициализации и риск случайного незаконного связывания временные, которые затем выходят за рамки (например, после неявного преобразования).

Да, интеллектуальные указатели, такие как повышающие, почти всегда являются правильным решением для обработки составных элементов, а иногда и для связанных элементов (shared_ptr).

person Pontus Gagge    schedule 10.04.2009
comment
Хехе, я знал, что кто-то уловит - ›замечание о синтаксисе - спасибо за подсказку, очень полезно. - person Nick Bolton; 10.04.2009
comment
IIRC, вы можете привязать временную только к константной ссылке, а затем временная останется на время ссылки. Назначьте указатель на временное, и тогда у вас возникнут проблемы. Ссылки здесь безопаснее. - person David Thornley; 10.04.2009
comment
@ Дэвид: Я совершил ту же ошибку. Время жизни временной привязки к const ref ограничено блоком кода. После выхода из блока кода (и, например, возврата объекта с привязкой const ref) эта ссылка становится бессмысленной. Вот почему ссылки выглядят безопаснее, чем указатели, но не всегда. - person Pontus Gagge; 10.04.2009
comment
См. GOTW # 88 Херба Саттера для получения более подробной информации о том, почему ссылки на участников могут быть опасными. - person Pontus Gagge; 10.04.2009

class Foo {
public:
    Bar bar1;
    Bar &bar2;
    Bar *bar3;

    // member bar2 must have an initializer in the constructor
    Bar::Bar(Bar& _bar2) : bar1(), bar2(_bar2), bar3(new Bar()) {}

    Bar::~Bar() {delete bar3;}
}

Обратите внимание, что bar2 не просто инициализируется в ctor; он инициализируется объектом bar, который передается в качестве ссылочного параметра. Этот объект и поле bar2 будут связаны вместе на время жизни нового объекта Foo. Обычно это очень плохая идея, потому что трудно гарантировать, что время жизни двух объектов будет хорошо скоординировано (т. Е. Что вы никогда не избавитесь от переданного объекта bar до удаления объекта Foo).

Вот почему очень предпочтительно использовать либо переменные экземпляра (как в bar1), либо указатели на объекты, размещенные в куче (как в bar3.)

person Dan Breslau    schedule 10.04.2009