Ссылки на наследование не работают правильно - PullRequest
0 голосов
/ 01 сентября 2018

При запуске следующего кода, который включает в себя изменение значения int с помощью метода класса с именем B, который наследуется от шаблонного класса A, значение int не изменяется, и я не могу понять, почему, Я проверил оба из них со стволом Clang и стволом GCC:

#include <iostream>

template<typename T>
struct A
{
    A(T& a_num_) : a_num(a_num_) {}

    T& a_num;
};

struct B : public A<int>
{
    template<typename... Args>
    B(Args... args) : A<int>(args...) {}

    void do_something()
    {
        a_num = 1634;
    }
};

int main(void)
{
    int num = 4;

    B b {num};
    b.do_something();

    std::cout << num;

    return 0;
}

Я бы ожидал получить 1634 отпечатка, но вместо 4 отпечатков.

Я сузил ошибку до конструктора B, потому что если я изменю конструктор B на:

B(int& x) : A<int>(x) {}

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

B b {num};

Тогда ему следует выбрать Args = [int &], потому что это единственный способ удовлетворить конструктор A, который принимает T &, так что же здесь происходит? Что такое a_num в этом случае? Просто ссылка на мусор, которая ничего не ссылается, или, возможно, временный объект?

Я также пытался переписать функцию do_something как

A<int>::a_num = 1634

но его все равно не удалось изменить.

Я также заметил, что объявление b как:

B b {6};

Также работает, хотя нет способа 6, который является значением pr, мог бы связываться со ссылкой на значение l.

Так что мой вопрос здесь заключается в том, почему конструктор B выбирает Args = [int] вместо Args = [int &] и как он может это сделать, когда затем передает Args конструктору, который принимает T &?

Ответы [ 2 ]

0 голосов
/ 01 сентября 2018

Вы должны изменить конструктор B на Args в качестве ссылки Args&...:

template<typename... Args>
B(Args&... args) : A<int>(args...) {}

Теперь ваш код будет печататься 1634, как вы изначально хотели.

Подтверждение адресов памяти

Если вы хотите подтвердить, что эти переменные указывают на один и тот же адрес памяти, вы можете сделать:

std::cout << &num << '\n';
std::cout << &b.a_num << '\n';

Это печатает тот же адрес памяти. Например:

0x7fff5508cac8
0x7fff5508cac8

Использование указателей

Поскольку идея состоит в том, чтобы заставить num и A::a_num указывать на один и тот же адрес памяти, стоит упомянуть решение, использующее указатели. В этом случае вам не нужно менять конструктор B, но вы должны изменить A::a_num на T*.

template<typename T>
struct A
{
    A(T* a_num_) : a_num(a_num_) {}

    T* a_num;
};

struct B : public A<int>
{
    template<typename... Args>
    B(Args... args) : A<int>(args...) {}

    void do_something() {
        *a_num = 1634;
    }
};

int main(void)
{
    int num = 4;

    B b {&num};
    b.do_something();

    std::cout << num;
}

Этот код также печатает 1634, как вы изначально хотели.

Редактировать : Я должен помнить, что этот код является плохой практикой и никогда не должен рекомендоваться для производственного использования. Адрес памяти локальных переменных никогда не должен передаваться, поскольку они действительны только в том случае, если область действия существует в стеке. Мы можем реализовать гораздо лучший код, используя умные указатели или просто копируя значения вместо повторного использования их адресов памяти.

0 голосов
/ 01 сентября 2018

Единственный раз, когда параметр типа шаблона будет выведен как ссылочный тип, это когда вы объявляете соответствующий параметр функции равным T&& и передаете lvalue функции.

Это означает, что в этом случае Args определяется как {int}. После создания конструктора B он выглядит следующим образом:

B(int arg) : A<int>(arg) {}

Это совершенно верно, поскольку arg является lvalue, и поэтому параметр a_num_ для конструктора A может связываться с ним. Затем вы копируете эту ссылку на элемент A *1013*, а затем параметр функции, с которым он связан, выходит за пределы области видимости, и a_num становится висячей ссылкой. Поэтому поведение при попытке назначить через a_num не определено.


Если вы хотите, чтобы конструктор B брал аргументы по ссылке, вам нужно определить тип аргумента как ссылку:

tmeplate <typename... Args>
B(Args&... args) : A<int>(args) {}

Если вы сделаете это, то b.a_num будет ссылкой на num, который вы определили в main, и все будет работать так, как вы ожидаете.

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