#c #c 11
#c #c 11
Вопрос:
Еще раз мне нужно сделать что-то вроде этого —
мне нужно сначала инициализировать вектор, поэтому я передаю его адрес data() родительскому классу.
#include <vector>
struct A{
A(int *a) : a(a){}
int *a;
};
struct B : A{
B() : A( v.data() ){}
std::vector<int> v { 1024 };
};
#include <cstdio>
int main(){
B b;
b.v[55] = 5;
printf("%dn", b.a == b.v.data() );
printf("%dn", b.a[55]);
printf("%dn", b.v[55]);
}
Проблема здесь в том, что вектор инициализируется после родительского класса и v.data()
содержит мусор. Я даже удивлен, что это компилируется.
Я знаю, что могу использовать композицию / декоратор для этого или защищенного элемента setPtr(int *a)
, но мне интересно, есть ли другой способ сделать это.
Пожалуйста, не комментируйте необработанные указатели. Это всего лишь пример зависимостей между родительским и дочерним классами.
Комментарии:
1. вы должны указать, что может измениться, а что нет.
v.data()
вы не можете использовать, но вы можете использоватьv
s-адрес или ссылку наv
, чтобы получитьdata
позже, послеv
создания. Какова фактическая цель? ЗачемA
нужен указатель?2. Я предполагаю, что базовый элемент на самом деле
private
?3. Могу ли я спросить, зачем базовому классу нужно знать или получать что-либо от подкласса? звучит как плохой дизайн.
4. Если предполагается, что A должен знать что-то о своих производных классах, это может быть подходящим кандидатом для CRTP.
5. Нет ничего плохого в использовании необработанного указателя здесь, поскольку это указатель, не являющийся владельцем. Память принадлежит вектору.
Ответ №1:
Базовые классы создаются сначала по дизайну. Обойти это невозможно. Однако вы можете ввести второй базовый класс и наследовать от своих базовых классов в желаемом порядке инициализации.
struct a
{
int x;
a(int x) : x(x) {};
};
struct b
{
int *px;
b(int* px) : px(px) {};
};
struct derived : a, b
{
public:
derived(int x_) : a(x_), b(amp;x) {};
};
Комментарии:
1. Это не сработает. x является локальной переменной в производном конструкторе и завершает область видимости при возврате конструктора. Экземпляр b будет иметь указатель на мусор. Вместо этого вы должны передать amp;this->A::x .
2. @Anonymous1847 Спасибо, у меня определенно есть некоторые плохие привычки, когда дело доходит до имен аргументов. Я исправил источник, добавив несколько символов подчеркивания.
3. Это идиома Base-from-Member . boost предоставляет base_from_member .
Ответ №2:
Почему бы не делегировать инициализацию для A какой-либо функции, отличной от конструктора?
#include <vector>
struct A{
A() : a(nullptr) {}
void init(int* i_a) {
a = i_a;
}
int *a;
};
struct B : A{
B() : A(), v(1024) {
init(v.data());
}
std::vector<int> v;
};
#include <cstdio>
int main(){
B b;
b.v[55] = 5;
printf("%dn", b.a == b.v.data() );
printf("%dn", b.a[55]);
printf("%dn", b.v[55]);
}
Кроме того, почему вы используете printf
в C ? Это довольно небезопасно. Используйте std::cout
вместо этого. Вопреки распространенному мнению, на самом деле это не намного медленнее, чем C stdio, если вы хорошо с этим справляетесь, и мы не уверены, что скорость ввода-вывода даже является проблемой для вашего кода.
Комментарии:
1. Спасибо за ответ, без обид, но я ненавижу «инициализацию». тогда что на самом деле делает c-tor?
printf
— быстрый, короткий, простой в использовании, по крайней мере, при использовании простых типов, таких какint
2. C-tor ничего не делает, инициализирует его в пустое состояние. Эффективным c-tor будет функция init .
std::cout
— быстро, короче, проще в использовании (std::cout << a
), более типобезопасно и меньше необходимости запоминать и отлаживать спецификации форматирования. 🙂3. И что тогда, если программист забыл позвонить
init()
? Вы правы для iostream.4. @Nick уже существует тесная связь между базовым и производным вашим дизайном. Я не вижу большой проблемы с вызовом
init
5. @Nick а что, если программист забыл инициализировать ваш вектор
v
с{ 1024 }
помощью ? У программиста всегда есть возможность неправильно использовать библиотеку, это не намного больше, чем у других. Просто не забудьте вызвать init() вскоре после построения.
Ответ №3:
Не предлагаю это как решение, но это может дать подсказку к реальному решению. Вы не можете использовать члены производного класса в конструкторе базового класса. Однако вы можете использовать ссылки или указатели на члены производного при построении базы. Вам нужно позаботиться о том, чтобы разыменовывать их только после того, как производный будет полностью сконструирован. Например, это «ок»:
#include <vector>
#include <iostream>
struct A{
A(std::vector<int>* p) : p(p){}
std::vector<int>* p;
};
struct B : A{
B() : A( amp;v ){} // <- fine as long as A only stores the pointer
std::vector<int> v { 1024 };
};
int main(){
B b;
b.v[55] = 5;
std::cout << (b.p == amp;b.v) << "n";
std::cout << (*b.p)[55];
}
Комментарии:
1. Это делает родительский класс «привязанным» к вектору. но, вероятно, если он будет шаблонизирован, он сможет передавать буферы другого типа, не std::array напрямую, а что-то вроде C 20 span или view (не уверен в правильном имени)
2. @Nick есть способы обойти это, я просто не хотел менять больше, чем необходимо, и не понимаю вашего варианта использования, распределители мне недоступны
Ответ №4:
Я выбрал любопытно повторяющийся шаблон шаблона
#include <iostream>
#include <vector>
#include <stdio.h>
template <typename T>
struct Base {
int* raw_ptr = nullptr;
void Supply() {
(static_cast<T*>(this))->Supply();
}
};
struct Derived : public Base<Derived> {
std::vector<int> v { 1024 };
void Supply() {
raw_ptr = v.data();
}
};
template<typename T>
void SupplyBuffer(Base<T>* b) {
b->Supply();
}
int main()
{
Derived b;
SupplyBuffer(amp;b);
b.v[55] = 5;
printf("%dn", b.raw_ptr == b.v.data() );
printf("%dn", b.raw_ptr[55]);
printf("%dn", b.v[55]);
return 0;
}