Рассмотрим следующий код:
class SmallObj {
public:
int i_;
double j_;
SmallObj(int i, double j) : i_(i), j_(j) {}
};
class A {
SmallObj so_;
int x_;
public:
A(SmallObj so, int x) : so_(so), x_(x) {}
int something();
int sox() const { return so_.i_; }
};
class B {
SmallObj* so_;
int x_;
public:
B(SmallObj* so, int x) : so_(so), x_(x) {}
~B() { delete so_; }
int something();
int sox() const { return so_->i_; }
};
int a1() {
A mya(SmallObj(1, 42.), -1.);
mya.something();
return mya.sox();
}
int a2() {
SmallObj so(1, 42.);
A mya(so, -1.);
mya.something();
return mya.sox();
}
int b() {
SmallObj* so = new SmallObj(1, 42.);
B myb(so, -1.);
myb.something();
return myb.sox();
}
Недостатками с подходом «А»:
- наше конкретное использование
SmallObject
делает нас зависимыми от его определения: мы не можем просто объявить его вперед ,
- наш экземпляр
SmallObject
является уникальным для нашего экземпляра (не общий),
Недостатков приблизиться к «B» несколько:
- нам нужно создать контракт собственности и сделать пользователь знает об этом,
- динамического распределения памяти должен выполняться перед каждым
B
создаются,
- косвенность требуется для доступа к элементам этого жизненно важного объекта,
- мы должны проверить нулевые указатели, если мы хотим, чтобы поддержать ваше дело конструктор по умолчанию,
- разрушение требует дальнейшего динамического вызова памяти,
Один из аргументов против использования автоматических объектов - это , стоимость которых составляет, передавая их по значению.
Это сомнительно: во многих случаях тривиальных автоматических объектов компилятор может оптимизировать эту ситуацию и инициализировать под-объект в строке. Если конструктор тривиален, он может даже сделать все в одной инициализации стека.
Вот -O3 реализация GCC по a1()
_Z2a1v:
.LFB11:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
subq $40, %rsp ; <<
.cfi_def_cfa_offset 48
movabsq $4631107791820423168, %rsi ; <<
movq %rsp, %rdi ; <<
movq %rsi, 8(%rsp) ; <<
movl $1, (%rsp) ; <<
movl $-1, 16(%rsp) ; <<
call _ZN1A9somethingEv
movl (%rsp), %eax
addq $40, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
Выделенные (; <<
) линии компилятор делает конструкцию в своем месте, и это SmallObj суб-объект в одном кадре.
и a2() оптимизирует очень сходно:
_Z2a2v:
.LFB12:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
subq $40, %rsp
.cfi_def_cfa_offset 48
movabsq $4631107791820423168, %rcx
movq %rsp, %rdi
movq %rcx, 8(%rsp)
movl $1, (%rsp)
movl $-1, 16(%rsp)
call _ZN1A9somethingEv
movl (%rsp), %eax
addq $40, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
И там есть б():
_Z1bv:
.LFB16:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA16
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl $16, %edi
subq $16, %rsp
.cfi_def_cfa_offset 32
.LEHB0:
call _Znwm
.LEHE0:
movabsq $4631107791820423168, %rdx
movl $1, (%rax)
movq %rsp, %rdi
movq %rdx, 8(%rax)
movq %rax, (%rsp)
movl $-1, 8(%rsp)
.LEHB1:
call _ZN1B9somethingEv
.LEHE1:
movq (%rsp), %rdi
movl (%rdi), %ebx
call _ZdlPv
addq $16, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 16
movl %ebx, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
.L6:
.cfi_restore_state
.L3:
movq (%rsp), %rdi
movq %rax, %rbx
call _ZdlPv
movq %rbx, %rdi
.LEHB2:
call _Unwind_Resume
.LEHE2:
.cfi_endproc
Очевидно, что в данном случае мы заплатили высокую цену, чтобы пройти мимо указателя вместо стоимость.
Теперь давайте рассмотрим следующий фрагмент кода:
class A {
SmallObj* so_;
public:
A(SmallObj* so);
~A();
};
class B {
Database* db_;
public:
B(Database* db);
~B();
};
Из приведенного выше кода, каково ваше ожидание собственности «SmallObj» в конструкторе? И каково ваше ожидание владения «Базой данных» в B? Вы собираетесь создавать уникальное соединение с базой данных для каждого создаваемого вами B?
Для дальнейшего ответа на ваш вопрос в пользу сырьевых указателей, мы должны смотреть не дальше, чем стандарт 2011 C++, который ввел понятие std::unique_ptr
и std::shared_ptr
, чтобы помочь решить собственности двусмысленности, которая существует с Cs strdup()
(возвращает указатель на копию строки, не забудьте освободить).
Перед комитетом по стандартизации предложат ввести observer_ptr
в C++ 17, который представляет собой оболочку, не являющуюся собственностью, вокруг необработанного указателя.
Используя их с привилегированным вашим подходом вводит много котельных листовой металл:
auto so = std::make_unique<SmallObject>(1, 42.);
A a(std::move(so), -1);
Мы знаем, здесь a
имеет право собственности на so
экземпляра зарезервированных, как мы явный образом предоставить ему право собственности через std::move
. Но все, что является явным, стоит персонажей. Контраст с:
A a(SmallObject(1, 42.), -1);
или
SmallObject so(1, 4.2);
A a(so, -1);
Так что я думаю, что в целом очень мало случае в пользу сырья указателей для небольших объектов для композиции. Вы должны пересмотреть свой материал, который приведет вас к заключению, так как кажется вероятным, что вы упустили или неправильно поняли факторы в рекомендации о том, когда использовать необработанные указатели.
's/appealing/appalling /' – juanchopanza
Школа должна преподавать вам о 'std :: unique_ptr' и' std :: shared_ptr'. – kfsone
@juanchopanza Ах, я имел в виду обращение ко мне. Исправлена. –