2009-11-12 4 views
5

Недавно я встретил классы, которые используют объект конфигурации вместо обычных методов настройки для конфигурации. Небольшой пример:Конфигурация structs vs seters

class A { 
    int a, b; 
public: 
    A(const AConfiguration& conf) { a = conf.a; b = conf.b; } 
}; 

struct AConfiguration { int a, b; }; 

В расквитаться:

  • Вы можете расширить свой объект и легко гарантировать разумные значения по умолчанию для новых значений без ваших пользователей когда-либо нужно знать об этом.
  • Вы можете проверить конфигурацию для согласованности (например, ваш класс допускает только некоторые комбинации значений)
  • Вы экономите много кода, пропустив сеттеры.
  • Вы получаете конструктор по умолчанию для указания конструктора по умолчанию для вашей структуры конфигурации и используете A(const AConfiguration& conf = AConfiguration()).

Обратные (ы):

  • Вы должны знать конфигурацию во время строительства и не можем изменить его позже.

Есть ли еще недостатки в этом, что мне не хватает? Если нет: почему это не используется чаще?

ответ

6

ли вы передавать данные по отдельности или в структуры является вопрос стиля и должен решаться на индивидуальной основе случае.

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

A(const AConfiguration& conf) : a(conf.a), b(conf.b) {} 

или

A(int a_, int b_) : a(a_), b(b_) {} 

не имеет значения, все, что много. (Там есть ряд параметров, где каждый предпочел бы первое, но число это - и будет ли хорошо разработан такой класс - это спорно.) Тем не менее, могу ли я использовать объект как этот

A a1(Configuration(42,42)); 
A a2 = Configuration(4711,4711); 
A a3(7,7); 

или должны сделать это

A urgh; 
urgh.setA(13); 
urgh.setB(13); 

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

1

Использование этого метода делает двоичную совместимость более сложной.

Если структура изменена (добавлено одно новое добавочное поле), все коды с использованием класса могут нуждаться в перекомпиляции. Если добавлена ​​новая функция не виртуального сеттера, такая перекомпиляция не требуется.

+0

Я недавно столкнулся с этим со старой базой кода, которую поддерживаю. Это очень раздражает. –

+0

Windows API делает это очень просто, добавив поле размера, убедившись, что структуры являются POD и только добавлением полей данных в конце. Добавьте поддерживающие старые конструкторы для создания новых структур (с разумными значениями по умолчанию для новых полей), а совместимость фактически _increased_. – sbi

+0

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

2

Основной недостаток заключается в том, что объект A может быть несовместимым. Я не знаю, имеет ли фактическое действие AConfiguration фактическое значение только для параметра a и b для конструктора.

4

Использование этого метода упрощает двоичную совместимость.

Когда версия библиотеки изменяется, и если конфигурация struct содержит ее, то конструктор может отличить, передается ли «старая» или «новая» конфигурация, и избегать «нарушения прав доступа»/«segfault» при доступе к несуществующим полям.

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

Пример:

//version 1 
struct AConfiguration { int version; int a; AConfiguration(): version(1) {} }; 
//version 2 
struct AConfiguration { int version; int a, b; AConfiguration(): version(2) {} }; 

class A { 
    A(const AConfiguration& conf) { 
    switch (conf.version){ 
     case 1: a = conf.a; b = 0; // No access violation for old callers! 
     break; 
     case 2: a = conf.a; b = conf.b; // New callers do have b member 
     break; 
    } 
    } 
}; 
0

Я бы поддержал уменьшенную двоичную совместимость здесь.

Проблема, которую я вижу, происходит от прямого доступа к полям структуры.

struct AConfig1 { int a; int b; }; 
struct AConfig2 { int a; std::map<int,int> b; } 

Поскольку я изменил представление b, я ввинчивается, а с:

class AConfig1 { public: int getA() const; int getB() const; /* */ }; 
class AConfig2 { public: int getA() const; int getB(int key = 0) const; /* */ }; 

Физическое расположение объекта может иметь изменения, но мои добытчики не имеют и смещение к функциям тоже нет.

Конечно, для двоичной совместимости следует проверить идиому PIMPL.

namespace details { class AConfigurationImpl; } 

class AConfiguration { 
public: 
    int getA() const; 
    int getB() const; 
private: 
    AConfigurationImpl* m_impl; 
}; 

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

Представление экземпляра в памяти не зависит от количества методов, это только зависит от:

  • наличия или отсутствия виртуальных методов
  • базовых классов
  • этих атрибутов

Что такое ВИДИМО (не то, что доступно).

И здесь мы гарантируем, что мы не будем изменять атрибуты. Определение AConfigurationImpl может измениться без каких-либо проблем, и реализация методов также может измениться.

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

Независимо от того, подходит оно вам, вы сами решаете.

Смежные вопросы