Инициализировать дочерний класс перед родительским классом без композиции / декоратора

#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;
}