Члены класса, которые являются объектами - указатели или нет? C ++ - PullRequest
36 голосов
/ 06 октября 2010

Если я создаю класс MyClass и в нем есть какой-то закрытый член, скажем, MyOtherClass, лучше сделать указатель MyOtherClass или нет? Что значит также иметь его как не указатель с точки зрения того, где он хранится в памяти? Будет ли объект создан при создании класса?

Я заметил, что примеры в QT обычно объявляют члены класса как указатели, когда они являются классами.

Привет

Mark

Ответы [ 11 ]

30 голосов
/ 06 октября 2010

Если я создаю класс MyClass и в нем есть какой-то закрытый член, скажем, MyOtherClass, лучше ли сделать MyOtherClass указателем или нет?

, как правило, следует объявить его как значениетвой класс.он будет локальным, будет меньше шансов на ошибки, меньше выделений - в конечном счете, будет меньше ошибок, и компилятор всегда может знать, что он существует с указанным смещением, так что ... это помогает оптимизировать и сокращать двоичные данные принесколько уровней.будет несколько случаев, когда вы знаете, что вам придется иметь дело с указателем (то есть полиморфным, разделяемым, требует перераспределения), как правило, лучше всего использовать указатель только тогда, когда это необходимо - особенно когда он закрыт / инкапсулирован.

Что значит также иметь его как не указатель с точки зрения того, где он хранится в памяти?

его адрес будет близок (или равен) this - gcc (например) имеет некоторые дополнительные параметры для вывода данных класса (размеры, таблицы, смещения)

Будет ли объект создан при создании класса?

да - размер MyClass будет увеличиваться на sizeof (MyOtherClass) или более, если компилятор перестроит его (например, в его естественное выравнивание)

23 голосов
/ 06 октября 2010

Где ваш член хранится в памяти?

Взгляните на этот пример:

struct Foo { int m; };
struct A {
  Foo foo;
};
struct B {
  Foo *foo;
  B() : foo(new Foo()) { } // ctor: allocate Foo on heap
  ~B() { delete foo; } // dtor: Don't forget this!
};

void bar() {
  A a_stack; // a_stack is on stack
             // a_stack.foo is on stack too
  A* a_heap = new A(); // a_heap is on stack (it's a pointer)
                       // *a_heap (the pointee) is on heap
                       // a_heap->foo is on heap
  B b_stack; // b_stack is on stack
             // b_stack.foo is on stack
             // *b_stack.foo is on heap
  B* b_heap = new B(); // b_heap is on stack
                       // *b_heap is on heap
                       // b_heap->foo is on heap
                       // *(b_heap->foo is on heap
  delete a_heap;
  delete b_heap;
  // B::~B() will delete b_heap->foo!
} 

Мы определяем два класса A и B.A хранит открытый член foo типа Foo.B имеет член foo типа pointer to Foo.

Какова ситуация для A:

  • Если вы создадите переменную a_stack типа A в стеке , тогда объект (очевидно), и его члены тоже находятся в стеке .
  • Если вы создадите указатель на A, как a_heap в приведенном выше примере, просто переменная указателя находится на стек ;все остальное (объект и его члены) находятся в куче .

Как выглядит ситуация в случае B:

  • вы создаете B в стеке : тогда и объект, и его член foo находятся в стеке , но объект, на который foo указывает (указатель)находится на куче .Вкратце: b_stack.foo (указатель) находится в стеке, но *b_stack.foo (указатель) находится в куче.
  • вы создаете указатель на B с именем b_heap: b_heap(указатель) находится в стеке, *b_heap (указатель) находится в куче , а также элемент b_heap->foo и *b_heap->foo.

Willобъект будет автоматически создан?

  • В случае A: Да, foo будет автоматически создан при вызове неявного конструктора по умолчанию Foo.Это создаст integer, но не его инициализирует (оно будет иметь случайное число)!
  • В случае B: Если вы опустите наши ctor и dtor, тогда foo (указатель) также будет создан и инициализирован случайным числом, которое означает, что оно будет указывать на случайное местоположение в кучеНо обратите внимание, что указатель существует!Также обратите внимание, что неявный конструктор по умолчанию не выделит вам что-то для foo, вы должны сделать это явно .Вот почему вам обычно требуется явный конструктор и сопровождающий деструктор для выделения и удаления указателя вашего члена-указателя.Не забывайте о семантике копирования : что происходит с pointee, если вы копируете объект (через конструкцию или назначение копирования)?

Какой смысл всего этого?

Существует несколько вариантов использования указателя на член:

  • Чтобы указать на объект, который вам не принадлежит.Допустим, вашему классу нужен доступ к огромной структуре данных, которую очень дорого копировать.Тогда вы можете просто сохранить указатель на эту структуру данных.Имейте в виду, что в этом случае создание и удаление структуры данных выходит за рамки вашего класса.Кто-то другой должен позаботиться.
  • Увеличение времени компиляции, так как в вашем заголовочном файле указатель не нужно определять.
  • Чуть более продвинутый;Когда в вашем классе есть указатель на другой класс, в котором хранятся все закрытые члены, «идиома Pimpl»: http://c2.com/cgi/wiki?PimplIdiom, также взглянет на Саттера, Х. (2000): Исключительно C ++ , p,99--119
  • И некоторые другие, посмотрите на другие ответы

Советы

Будьте особенно осторожны, если ваши участники являются указателями, а вы ими владеете.Вы должны написать правильные конструкторы, деструкторы и подумать о конструкторах копирования и операторах присваивания.Что происходит с pointee, если вы копируете объект?Обычно вам также нужно скопировать конструкцию pointee!

17 голосов
/ 06 октября 2010

В C ++ указатели являются объектами сами по себе. Они на самом деле не привязаны к тому, на что они указывают, и нет особого взаимодействия между указателем и его указателем (это слово?)

Если вы создаете указатель, вы создаете указатель и ничего больше . Вы не создаете объект, на который он может или не может указывать. И когда указатель выходит из области видимости, объект, на который указывает указатель, остается неизменным. Указатель никоим образом не влияет на время жизни того, на что он указывает.

Так что в общем случае не следует использовать указатели по умолчанию. Если ваш класс содержит другой объект, этот другой объект не должен быть указателем.

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

6 голосов
/ 06 октября 2010

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

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

4 голосов
/ 06 октября 2010

Я следую следующему правилу: если объект-член живет и умирает вместе с инкапсулирующим объектом, не используйте указатели. Вам понадобится указатель, если объект-член по какой-то причине должен пережить инкапсулирующий объект. Зависит от поставленной задачи.

Обычно вы используете указатель, если объект-член предоставлен вам, а не создан вами. Тогда вам, как правило, тоже не нужно его уничтожать.

4 голосов
/ 06 октября 2010

Этот вопрос можно обсуждать бесконечно, но основы:

Если MyOtherClass не является указателем:

  • Создание и уничтожение MyOtherClass происходит автоматически, что может уменьшить количество ошибок.
  • Память, используемая MyOtherClass, является локальной для MyClassInstance, что может повысить производительность.

Если MyOtherClass является указателем:

  • Ответственность за создание и уничтожение MyOtherClass лежит на вас
  • MyOtherClass может иметь значение NULL, что может иметь значение в вашем контексте и может сэкономить память
  • Два экземпляра MyClass могут использовать один и тот же MyOtherClass
3 голосов
/ 06 октября 2010

Некоторые преимущества члена указателя:

  • Дочерний (MyOtherClass) объект может иметь время жизни, отличное от его родительского (MyClass).
  • Возможно, объект может использоваться несколькими несколькими MyClass(или другие) объекты.
  • При компиляции файла заголовка для MyClass компилятору не обязательно знать определение MyOtherClass.Вам не нужно включать его заголовок, что сокращает время компиляции.
  • Уменьшает размер MyClass.Это может быть важно для производительности, если ваш код много копирует объекты MyClass.Вы можете просто скопировать указатель MyOtherClass и внедрить какую-то систему подсчета ссылок.

Преимущества использования элемента в качестве объекта:

  • Вам не нужно явно указыватьнаписать код для создания и уничтожения объекта.Это проще и менее подвержено ошибкам.
  • Делает управление памятью более эффективным, поскольку вместо двух должен быть выделен только один блок памяти.
  • Реализация операторов присваивания, конструкторов копирования / перемещения и т. Д.намного проще
  • Более интуитивно понятный
1 голос
/ 06 октября 2010

Преимущество родительского класса, поддерживающего отношение к объекту-члену в виде указателя (std :: auto_ptr) на объект-член, состоит в том, что вы можете пересылать объявление объекта, а не включать файл заголовка объекта.

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

Когда вы используете auto_ptr, вам нужно только позаботиться о построении, что вы обычно можете сделать в списке инициализатора. Уничтожение вместе с родительским объектом гарантируется auto_ptr.

1 голос
/ 06 октября 2010

Это зависит ...: -)

Если вы используете указатели, чтобы сказать class A, вам нужно создать объект типа A, например, в конструкторе вашего класса

 m_pA = new A();

Кроме того, не забудьте уничтожить объект в деструкторе, или у вас есть утечка памяти:

delete m_pA; 
m_pA = NULL;

Вместо этого проще объединить объект типа A в вашем классе, вы можете 'не забудьте уничтожить его, потому что это делается автоматически в конце срока службы вашего объекта.

С другой стороны, наличие указателя имеет следующие преимущества:

  • Если ваш объект размещен в стеке, а тип A использует много памяти, это выиграетВы можете быть выделены из стека, но из кучи.

  • Вы можете построить свой объект A позже (например, в методе Create) или уничтожить его ранее (в методе Close)

1 голос
/ 06 октября 2010

Если вы делаете объект MyOtherClass в качестве члена вашего MyClass:

size of MyClass = size of MyClass + size of MyOtherClass

Если вы делаете объект MyOtherClass в качестве члена указателя вашего MyClass:

size of MyClass = size of MyClass + size of any pointer on your system

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

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