C ++ Динамическое распределение памяти в шаблонной функции - PullRequest
0 голосов
/ 13 октября 2019

У меня есть этот класс и шаблон

class MyClass{
public:
    int data;
};

template< typename Tp> Tp makeObj()
{
    /// How is this allocated
    Tp obj;
    obj->data = 10;
    return obj;
}

И основная функция

int main()
{    
    /// Remove the next line and "makeObj" will return a null ptr
    /// I have no idea why    
    std::vector<int> vec;
    MyClass *tmp = makeObj<MyClass*>();
    std::cout << tmp->data << std::endl;
    std::cout << "\n\n | DONE3 -> " << std::endl;
    getchar();
    return 0;
}

Функция "makeObj" возвращает нулевой ptr, но когда я добавляю "std :: vector""line, он возвращает правильный ptr, затем я удаляю его снова, он возвращает нулевой ptr.

Мой вопрос: как этот ptr выделен? и это ошибка?

Ответы [ 3 ]

1 голос
/ 13 октября 2019

Давайте возьмем имеющуюся у вас функцию шаблона

template< typename Tp> Tp makeObj()
{
    /// How is this allocated
    Tp obj;
    obj->data = 10;
    return obj;
}

и разверните ее с помощью типа

MyClass* makeObj()
{
    /// How is this allocated
    MyClass* obj;
    obj->data = 10;
    return obj;
}

Теперь вы можете ясно видеть, что вы на самом деле не выделяете MyClassобъект, только указатель на него. Этот указатель будет неинициализирован и разыменование приведет к неопределенному поведению .

Одно из возможных решений - не указывать аргумент шаблона, а указывать тип, а затем явно использовать указатель наэто в шаблоне функции:

// Declare the return-type to be a pointer
//                       v
template< typename Tp> Tp* makeObj()
{
    /// How is this allocated
    Tp* obj = new Tp;  // Declare variable as a pointer,
                       // and actually create an instance of the type
    obj->data = 10;
    return obj;
}

Используйте его как

// Explicitly declares as a pointer
//      v
MyClass *tmp = makeObj<MyClass>();
//                     ^^^^^^^
// And pass the plain class-type as the template argument, not a pointer
0 голосов
/ 13 октября 2019

Это действительно просто: то, как вы его используете, Tp рассматривается как значение. Неважно, что это может быть указатель, поскольку указатели являются значениями. Вы можете думать о указателе как intptr_t с некоторыми добавленными дополнениями. Итак, makeObj делает экземпляр указателем . Но этот указатель ни на что не указывает. Итак, obj->data = 10 - неопределенное поведение. И с этого момента ваш код может делать что угодно. На этом анализ заканчивается. Вам нужно исправить makeObj. Я предполагаю, что makeObj должна быть фабричной функцией.

Ниже приведен пример того, как makeObj может выглядеть. Вы действительно не хотите возиться с необработанными указателями. Заводские функции должны возвращать std::unique_ptr или эквивалент (например, kj::Own из capnproto ). Вот как должен выглядеть идиоматический современный C ++.

Вам также не нужно использовать консольный ввод-вывод для подтверждения того, что все работает должным образом: вы можете assert, и отладчик остановится в точке, гдеутверждение не удается. От этого до разработки, основанной на тестировании, есть небольшой шаг;вы бы использовали вариант «assert», который специфичен для используемой тестовой среды. Но даже без тестовой среды легко проверить вещи, используя assert, когда вы играете с вещами.

Учитывая, что мы живем в прекрасные времена, когда вы можете получить доступ к компилятору и отладчику C ++ в браузере, выможете поэкспериментировать с кодом ниже на https://onlinegdb.com/BJZOB6gKH

#include <cassert>
#include <memory>

template <class T, class... Args> std::unique_ptr<T> makeObj(Args &&... args) {
  auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
  ptr->data = 10;
  return ptr;
}

struct MyType { 
    int foo = {};
    int data = {};
    MyType() = default;
    MyType(int foo, int data) : foo(foo), data(data) {}
};

int main()
{
    auto t = makeObj<MyType>(5, 20);
    assert(t->foo == 5);
    assert(t->data == 10);
    auto u = makeObj<MyType>();
    assert(u->foo == 0);
    assert(u->data == 10);
}
0 голосов
/ 13 октября 2019

Ваша функция не возвращает действительный указатель, вместо этого она возвращает все, что происходит в стеке. Содержимое стека внутри make-_obj изменяется путем включения «std :: vector vec;»line.

То, что у вас есть, это чисто неопределенное поведение.

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