Условное объявление объектов, наследуемых от общего базового класса для передачи ссылки на один из них - PullRequest
0 голосов
/ 22 апреля 2020

Скажем, у меня есть два класса, унаследованных от общей базы, таких как

class Thing{
public:
    virtual void f()=0;
};

class Thing_variant_a: public Thing{
public:
    void f(){
        std::cout<<"I am (a)"<<std::endl;
    }
};

class Thing_variant_b: public Thing{
public:
    void f(){
        std::cout<<"I am (b)"<<std::endl;
    }
};

И функция, принимающая ссылку на объект Thing в качестве аргумента.

void function(Thing& t){
    t.f();
}

В зависимости от условий я хотел бы позвонить function с thing_a или thing_b (и, возможно, расширить это в какой-то момент, добавив еще одну возможность thing_c)

Я знаю, что могу сделать это, используя указатель

Thing *t = nullptr;
if(condition_a){
    t = new Thing_variant_a();
} else if(condition_b){
    t = new Thing_variant_b();
}

function(*t);

Однако я хотел бы знать, если есть лучший способ, что

  • не выделяет кучу памяти

  • не требует, чтобы я позаботился об удалении t в какой-то момент (вероятно, умные указатели, но я не знаю много о них)

  • гарантирует, что я всегда передать действительную Thing ссылку на функцию (в сложной структуре может быть больше условных выражений, чем в этом минимальном примере), я мог бы сделать if(t){ function(*t);}else{/*handle error*/}), но, похоже, должно быть более элегантное решение.

Если не все вышеперечисленное возможно, любая комбинация n из тех?

Ответы [ 3 ]

1 голос
/ 22 апреля 2020

Я постараюсь ответить на каждый ваш вопрос

не выделяет кучу памяти

К сожалению, c ++ поддерживает полиморфизм только с помощью указателей. Я предполагаю, что проблема, с которой вы столкнетесь здесь, заключается в фрагментации памяти (то есть, ваши указатели повсюду в куче). Лучший способ справиться с этим - выделить память с помощью пула памяти.

Вы можете использовать std::variant, но вам все равно нужно будет проверить доступный в настоящее время тип в варианте.

не требует, чтобы я позаботился об удалении t в какой-то момент (возможно, умные указатели, но я не знаю много о них)

Вы можете использовать std::unique_ptr, который будет в основном называется деструктором, когда никто больше не держит этот указатель.

гарантирует, что я всегда передаю действительную ссылку Thing на функцию (в сложной структуре может быть больше условных выражений, чем в этом минимальном примере), что я мог бы сделать if (t) {function (* t);} else {/ обработать ошибку /}), но, похоже, должно быть более элегантное решение.

Если вы используйте указатели, которые вы можете просто проверить на nullptr, как вы делаете сейчас. Я не уверен, что вы подразумеваете под действительной ссылкой, поскольку ссылка всегда указывает на что-то и не может быть пустой.

1 голос
/ 22 апреля 2020

Это очень похоже на проблему XY. Вероятно, существует совершенно иное решение вашей проблемы.

C ++ - язык со статической типизацией; это означает, что типы, используемые в данном пути кода, фиксируются во время компиляции. Типы Dynami c (типы, известные во время выполнения) обычно распределяются через кучу или все сразу, а затем выбираются во время выполнения.

Так что, как вы заметили, в вашем случае мало что возможно ..

Например, вы можете просто использовать два разных пути кода:

if (condition_a) {
    Thing_variant_a a;
    function(a);
} else if (condition_b) {
    Thing_variant_a b;
    function(b);
}

Предварительно выделить типы:

Thing_variant_a a;
Thing_variant_a b;

if (condition_a) {
    function(a);
} else if (condition_b) {
    function(b);
}

Или использовать шаблон:

template<typename T>
void do_something() {
    T t;
    function(t);
}

// somewhere else in the code ...
do_something<Thing_variant_a>();

// or ...
do_something<Thing_variant_b>();

Вот способ использования динамической c памяти и unique_ptr:

std::unique_ptr<Thing> t;
if (condition_a) {
    t = std::make_unique<Thing_variant_a>();
} else if (condition_b) {
    t = std::make_unique<Thing_variant_b>();
}

function(*t);

// t is delete'd automatically at end of scope...

И, кстати, такая функция, как int f(){...}, должна возвращать некоторое значение int.

1 голос
/ 22 апреля 2020

Вот способ сделать это без использования кучи или указателей:

Thing_variant_a thingA;
Thing_variant_b thingB;

if(condition_a){
    function(thingA);
} else if(condition_b){
    function(thingB);
}

Если вы хотите, вы уменьшите его до одного вызова через троичного оператора:

Thing_variant_a thingA;
Thing_variant_b thingB;
function(condition_a ? static_cast<Thing &>(thingA) : static_cast<Thing &>(thingB));

Что касается ссылок go, ссылки в C ++ должны всегда быть ненулевыми - поэтому, если вы пытаетесь разыменовать нулевой указатель (например, вызывая function(*t), когда t==NULL), вы уже вызвали undefined поведение и обречены; код внутри function() ничего не может сделать, чтобы спасти вас. Так что, если есть какое-либо изменение, что ваш указатель имеет значение NULL, вы должны проверить это , прежде чем разыменовывает его.

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