Современный способ иметь карту, которая может указывать или ссылаться на данные разных типов, которые были выделены в стеке - PullRequest
1 голос
/ 14 февраля 2020

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

#include <iostream>
#include <string>
#include <map>

using namespace std;

class base {
public:
    virtual ~base(){}
};

class derived1 : public base {
public:
    virtual ~derived1(){}
    derived1(const int& other) : val(other) {}
    int val;
};

class derived2 : public base {
public:
    virtual ~derived2(){}
    derived2(const float& other) : val(other) {}
    float val;
};


int main()
{
    derived1 dataA = 4;
    derived2 dataB = 3.0f;

    std::map<std::string, base*> mapOfPointersToData; //Needs to be a pointer because it can be of type deribed1 or derived2. Cant be smart as pointing to stack memory
    mapOfPointersToData["dataA"] = &dataA;
    mapOfPointersToData["dataB"] = &dataB;

    base* result = mapOfPointersToData["dataA"];

    std::cout << dynamic_cast<derived1*>(result)->val << std::endl;

    return 0;
}

Это работает, но мне сказали, что нам следует избегать использования необработанных указателей в пользу умных указателей или ссылок.

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

int main()
{
    derived1 dataA = 4;
    derived2 dataB = 3.0f;

    std::map<std::string, std::variant<derived1, derived2>&> mapOfPointersToData; //Cant be constructed
    mapOfPointersToData["dataA"] = dataA;
    mapOfPointersToData["dataB"] = dataB;

    auto result = mapOfPointersToData["dataA"];

    std::cout << std::get<derived1>(result).val << std::endl;

    return 0;
}

, но это дает ошибку

error: value-initialization of reference type ‘std::variant<derived1, derived2>&

Так что это лучший способ хранить ссылки на данные стека, которые могут быть разных типов?

Ответы [ 2 ]

5 голосов
/ 14 февраля 2020

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

Необработанные указатели не приветствуются, только когда они несут ответственность за владение тем, на что они указывают (по сути, если вы должны помнить delete или иначе отпустить их). Здесь объекты имеют автоматы c длительность хранения , и указатели не несут ответственности за их очистку. Это разумный вариант использования необработанных указателей.

Объекты никогда не могут быть нулевыми, поэтому имеет смысл использовать ссылку.

Объекты на вашей карте могут быть nullptr, если вы используете operator[] для поиска по карте. Этот оператор добавляет любой элемент, который он не может найти, и значение инициализирует их (до nullptr для указателей). Вы можете использовать find вместо этого, если вы не хотите раздувать вашу карту пустыми указателями.

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

Возможно, вы искали std::reference_wrapper, который оборачивает ссылки в объект, подходящий для контейнеров. Но использование base* уже кажется хорошим решением, потому что ваша карта не владеет объектами, на которые она в конечном итоге указывает.

1 голос
/ 14 февраля 2020

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

int main()
{
    std::map<std::string, std::unique_ptr<base>> mapOfPointersToData;
    mapOfPointersToData["dataA"] = std::make_unique<derived1>(4);
    mapOfPointersToData["dataB"] = std::make_unique<derived2>(3.0f);

    auto& result = mapOfPointersToData["dataA"];

    std::cout << dynamic_cast<derived1*>(result.get())->val << std::endl;

    return 0;
}

Обратите внимание, что я помещаю создание объекта в кучу, используя make_unique<>() вызовы для выделения владение умным указателем.

...