Как правильно получить ресурсы с условно-инициализированными объектами в C ++?

0

У меня есть несколько классов, которые я предоставил с помощью функции Init(). Учитывая, что программист, использующий этот код, может забыть вызвать Init(), или может вызвать деструктор до Init(), я написал свой деструктор, чтобы проверить состояние объекта. Я чувствую, что это не очень хорошо написано и хочет отойти от этого, если это возможно, путем инициализации и выделения всего необходимого в конструкторе (Скотт Мейерс рекомендует также против методов инициализации). Тем не менее я пытался найти способ сделать это при использовании дополнительных аргументов/нескольких конструкторов.

Текущий код:

class A {
    Init(B* some_other_object);
    Init(B* some_other_object, C* an_optional_argument);
    ...
}

int main(int argc, const char* argv[]) {
    ...
    A a;
    if(somecase)
        a.Init(b1, c1);
    else
        a.Init(b1);
}

Желаемый код:

class A {
    explicit A(B* some_other_object);
    A(B* some_other_object, C* an_optional_argument);
    ...
}

int main(int argc, const char* argv[]) {
    ...
    if(somecase)
        A a(b1, c1);
    else
        A a(b1);
}

Конечно, проблема здесь в том, что переменная a выходит из сферы действия. И поэтому я прибегаю к распределению кучи.

int main(int argc, const char* argv[]) {
    ...
    A* a;
    if(somecase)
        a = new A(b1, c1);
    else
        a = new A(b1);
    ...
    delete a;
}

Это не здорово, если я пытался сохранить это в стеке. Я также рассмотрел следующее

A a(b1, somecase?c1:nullptr);

Затем сдвиг условной составляющей в тело одного конструктора. Это не выглядит ужасно изящным. Есть ли лучший способ сделать то, что я пытаюсь сделать здесь? Предпочтительно дать мне возможность выделить A в стеке.

Теги:
constructor

7 ответов

7
Лучший ответ

Вы можете использовать для этого условный оператор. Это позволяет вам делать то, что вы не можете сделать с if-else, например. Предполагая этот простой класс с двумя конструкторами,

struct Foo
{
  explicit Foo(int) {}
  Foo(int, int) {}
};

вы можете инициализировать экземпляр класса, основываясь на таком состоянии:

int i = ....;

Foo f = (i==42) ? Foo(42) : Foo(1, 2);
  • 1
    +1 за предельную простоту и чистоту
  • 0
    Brilliant! Я думаю, что я могу использовать этот подход в моем коде. Большое спасибо.
2

Вот еще один вариант:

A handleSomeCase(bool someCase, B *b1, C *c1) {
    if(somecase)
        return A(b1, c1);
    else
        return A(b1);
}

int main() {
    ...
    A a = handleSomeCase(someCase, b1, c1);
}
  • 0
    Интересный подход. Правильно ли я думаю, что А будет скопировано здесь? Или копирование elision предотвратит это?
  • 0
    @rod Скопировать / переместить elision может устранить все копии и перемещения здесь. пример
1

Поскольку слишком длинные функции никогда не бывают хорошими, вместо задержки инициализации с помощью Init() и этого кода:

int main(int argc, const char* argv[]) {
    ...
    A a;
    if(somecase)
        a.Init(b1, c1);
    else
        a.Init(b1);
}

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

void caseA(B* b1, C* c1) {
    A a(b1, c1);
    ...
}

void caseB(B* b1) {
    A a(b1);
    ...
}

int main(int argc, const char* argv[]) {
    ...
    if(somecase)
        caseA(b1, c1);
    else
        caseB(b1);
}
1

Я бы сделал это:

C* c1 = null; // by default
if (somecase)
    c1 = new C(); // (etc: I don't know where your C object comes from!)

// Now there only 1 place you create this object, which is far cleaner    
A a(b1, c1);

И дополнительно (не то, что он применяется в моем коде выше) добавьте аргумент по умолчанию, а затем создайте один конструктор для class A:

 A(B* some_other_object, C* an_optional_argument = null);
  • 0
    Это мой нынешний подход. Последний пример в моем оригинальном посте: A a (b1, somecase? C1: nullptr); был в этом направлении. Я пытался понять, как я могу это сделать, не передавая нулевые аргументы. Но, возможно, я обдумываю это. По твоим словам, это не слишком плохой вариант.
1

Если объект скопирован, вы можете сделать следующее:

 class A
 {
 public:
     A();
     A(B* b);
     A(B* b, C* c);

     // you'll likely want to specify pointer ownership semantics
     A& operator=(const A& a);
 };

 A a;
 if(somecond)
     a = A(b);
 else
     a = A(b, c);

Чтобы избежать проблем с правами на указатель, рассмотрите std :: shared_pointer вместо обычных указателей во время копирования объекта.

  • 0
    Очень умный действительно. Хотя мне кажется, что это все еще имеет проблему Init (): а именно, если программист использует это неправильно и деструктор вызывается сразу после первой инициализации, нам все еще нужен умный деструктор. Правильно ли я думаю об этом?
  • 0
    @rod, просто заставь конструктор по умолчанию оставить объект пустым. Если ни одна его часть не настроена, то нечего сносить.
0

Если A имеет разумное состояние по умолчанию, я не вижу, что неправильно с вашим оригинальным методом. Он аналогичен следующему коду

string s;
if (x)
{
    s = "hi";
}
else
{
    s = "bye";
}

Проблема в том, что у вас есть наполовину построенные объекты, плавающие вокруг. Полностью построенный, но еще не содержащий полезные данные, объект в порядке (по моему скромному мнению)

  • 0
    С ОП звучит так, будто начальное состояние не имеет смысла; Вот почему он должен проверить в деструкторе, был ли объект приведен в разумное состояние с помощью Init () или нет.
0

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

class A {
    A(B* some_other_object, C* an_optional_argument = NULL) {
        if(an_optional_argument) {
            //...
        } else {
            //...
        }
    }
    ...
}

int main(int argc, const char* argv[]) {
    ...
    A a(b1);
    A b(b1, c1);
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню