Static_cast преобразуется в неправильный тип данных, но результат все еще правильный? - PullRequest
0 голосов
/ 27 января 2020

Основываясь на комментариях к моему последнему вопросу ( Getter-функция для производного класса в производном классе, при использовании указателя на базовый класс ), мне сказали, что я должен использовать static_cast при перезаписи моего указатель от базового класса на производный класс, чтобы получить доступ к производному классу. Я проверил это, используя следующий код:

#include <iostream>

class class_data{
public:
    int val_a = 0;
    double val_b = 0.;
};

class overridden_class_data : public class_data{
public:
    int val_c = 0.;
}; 

class overridden_class_data_II : public class_data{
public:
    int val_c = 12.;
}; 

class BaseClass{
public:
    BaseClass(){};

    virtual void print_data() = 0;
    virtual class_data *get_local_data() = 0;

    class_data local_class_data;
};

class DerivedClass : public BaseClass{
public:
    DerivedClass(){
        local_class_data.val_a = 10;
        local_class_data.val_b = 100.;
        local_class_data.val_c = 14;
    };

    void print_data() override{
        std::cout << "Hello World\n";
    }

    class_data * get_local_data() override {
        return &local_class_data;
    }

    overridden_class_data local_class_data;
};

class DerivedClassII : public BaseClass{
public:
    DerivedClassII(){
        local_class_data.val_a = 10;
        local_class_data.val_b = 100.;
    };

    void print_data() override{
        std::cout << "Hello World\n";
    }

    class_data * get_local_data() override {
        return &local_class_data;
    }

    overridden_class_data_II local_class_data;
};

void test_func(BaseClass *class_pointer){
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    std::cout << class_pointer->local_class_data.val_a << '\n';
    class_pointer->local_class_data.val_a = 5;
    std::cout << class_pointer->local_class_data.val_a << '\n';
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    class_pointer->get_local_data()->val_a = 15;
    std::cout << class_pointer->local_class_data.val_a << '\n';
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    std::cout << static_cast<overridden_class_data*>(class_pointer->get_local_data())->val_c << '\n';
}

int main(void){
    std::cout << "From main\n";
    DerivedClass DClass;
    DerivedClassII EClass;
    std::cout << "DClass: \n";
    test_func(&DClass);
    std::cout << "EClass: \n";
    test_func(&EClass);
    return 0;
}

Здесь у меня есть два производных класса, которые используют два различных производных класса в качестве переменной класса. Чтобы получить доступ к данным этих классов, я должен использовать static_cast на возвращенный указатель базового класса, чтобы привести его обратно к производному классу. Тем не менее, я не хочу переписывать функцию test_func() для обоих классов, но вместо этого использую для них одну и ту же функцию.

Изначально я подумал, что мне пришлось дважды написать последнюю строку функции, переписав указатель переменной класса один раз на overridden_class_data* и один раз на overridden_class_data_II*, в зависимости от входного класса. Но после тестирования я заметил, что мне не нужно этого делать, я могу изменить его на overridden_class_data*, но он все равно действует, как если бы я преобразовал его overridden_class_data_II*. Почему? Это потому, что оба класса содержат одинаковые элементы, и, следовательно, указатель может указывать на одну и ту же точку?

1 Ответ

1 голос
/ 27 января 2020

Что касается вашего исходного вопроса, то да, это происходит только потому, что (1) члены данных вашего класса настроены одинаково и (2) static_cast небезопасен для таких полиморфных c приведений.

A простой контрпример к разрыву test_func будет ( code ):

class overridden_class_data : public class_data{
public:
    int val_pad = 0.;
    int val_c = 23.;
}; 

, который будет некорректно выводить значение 0 вместо 12 для указателя EClass-> get_local_data () -> val_ c.

Несколько способов go решить эту проблему (одноразовое использование test_fn):

  1. Правильно определить вышеуказанную проблему с помощью dynamic_casts, но затем test_fun c должен вызываться с соответствующими явными аргументами шаблона.

  2. Fore go приведение типов с безопасностью во время компиляции путем создания простого обобщенного c test_fun c и использования ковариантных типов возврата. Вы упомянули, что вас беспокоит слишком большое количество шаблонов. Вас волнует код:

  3. @ churill предлагает использовать виртуальный метод получения, такой как get_val_c.

Вот фрагмент предложенного второго метода - я отметил внесенные изменения ( код ):

#include <iostream>

class class_data{
public:
    int val_a = 0;
    double val_b = 0.;
};

class overridden_class_data : public class_data{
public:
    int val_pad = 23;
    int val_c = 0.;
}; 

class overridden_class_data_II : public class_data{
public:
    int val_c = 12.;
};

class BaseClass{
public:
    BaseClass(){};

    virtual void print_data() = 0;
    virtual class_data *get_local_data() = 0;

    class_data local_class_data;
};

class DerivedClass : public BaseClass{
public:
    DerivedClass(){
        local_class_data.val_a = 10;
        local_class_data.val_b = 100.;
        local_class_data.val_c = 14;
    };

    void print_data() override{
        std::cout << "Hello World\n";
    }

    // use covariant return type    
    overridden_class_data * get_local_data() override {
        return &local_class_data;
    }

    overridden_class_data local_class_data;
};

class DerivedClassII : public BaseClass{
public:
    DerivedClassII(){
        local_class_data.val_a = 10;
        local_class_data.val_b = 100.;
    };

    void print_data() override{
        std::cout << "Hello World\n";
    }

    // use covariant return type
    overridden_class_data_II * get_local_data() override {
        return &local_class_data;
    }

    overridden_class_data_II local_class_data;
};

template <typename T>
void test_func(T *class_pointer){ // make generic
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    std::cout << class_pointer->local_class_data.val_a << '\n';
    class_pointer->local_class_data.val_a = 5;
    std::cout << class_pointer->local_class_data.val_a << '\n';
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    class_pointer->get_local_data()->val_a = 15;
    std::cout << class_pointer->local_class_data.val_a << '\n';
    std::cout << class_pointer->get_local_data()->val_a << '\n';
    std::cout << class_pointer->get_local_data()->val_c << '\n';
}

int main(void){
    std::cout << "From main\n";
    DerivedClass DClass;
    DerivedClassII EClass;
    std::cout << "DClass: \n";
    test_func(&DClass);
    std::cout << "EClass: \n";
    test_func(&EClass);
    return 0;
}
...