Хорошо, чтобы предоставить конструктор для агрегатов без поведения (bundle-o-data) в C++?

Пожалуйста, обратитесь к правилу № 41 C++ Standards Standards или Sutter's Gotw # 70, которое гласит:

Сделайте элементы данных частными, за исключением агрегатов без поведения (структуры в стиле C).

Я часто хотел бы добавить простой конструктор к этим структурам в стиле C, для удобства. Например:

struct Position
{
    Position(double lat=0.0, double lon=0.0) : latitude(lat), longitude(lon) {}
    double latitude;
    double longitude;
};

void travelTo(Position pos) {...}

main()
{
    travelTo(Position(12.34, 56.78));
}

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

Может быть, я могу последовать примеру std::pair и предоставить бесплатную функцию "makePosition"? NRVO должен сделать это так же быстро, как конструктор, верно?

Position makePosition(double lat, double lon)
{
    Position p;
    p.latitude = lat;
    p.longitude = lon;
    return p;
}

travelTo(makePosition(12.34, 56.78));

Я иду против духа концепции "совокупности без поведения", добавив этот жалкий маленький конструктор?

РЕДАКТИРОВАТЬ:

Да, я знал Position p={12.34, 56.78}, Но я не могу сделать travelTo({12.34, 56.78}) с чистыми структурами С.

РЕДАКТИРОВАТЬ 2:

Для тех, кто интересуется типами POD: что такое типы POD в C++?

ПОСЛЕДУЮЩАЯ ДЕЯТЕЛЬНОСТЬ: Я задал здесь дополнительный вопрос, тесно связанный с этим.

5 ответов

Решение

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

Альтернативы - стиль инициализации в фигурных скобках

Position p = {a,b};

или бесплатная функция "сделать"

Position makePosition(double a, double b)
{
    Position p = {a,b};
    return p;
}

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

void func(Position p)
{
    // ...
}

// func({a,b}) is an error

последнее прекрасно в этом случае, но для ленивого программиста это немного больше. Проблема с последней формой (функцией make) заключается в том, что она оставляет возможность забыть инициализировать структуру данных. Поскольку неинициализированные переменные вызывают у меня чувство дискомфорта, я предпочитаю определять конструктор для своих агрегатных типов.

Основная причина, по которой существует std::make_pair, на самом деле не по этой причине (std::pair имеет конструкторы), а на самом деле потому, что для вызова конструктора типа шаблона вы должны передать аргументы шаблона - что неудобно:

std::pair<int,int> func()
{
    return std::pair<int,int>(1,2);
}

Наконец, в вашем примере вы должны по крайней мере сделать ваш конструктор явным

explicit Position(double lat=0.0, double lon=0.0)

в противном случае вы допускаете неявное приведение к позиции из двойного

Position p = 0.0;

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

Я обычно предоставляю структуры конструктору, без проблем. Однако, если конструктор "нетривиален", то структура больше не считается типом POD, и будут ограничения на то, что вы можете с ней делать. Если это проблема для вас (это никогда не было для меня), тогда, очевидно, стоит использовать функцию make_XXXX.

Обратите внимание, что без конструктора следующее уже делает то, что вам нужно:

int main()
{
  Position pos = { 12.34, 56.78 };
  travelTo(pos);

  Position pos2 = {}; // zero initialises
  travelTo(pos2);
}

Концептуально это нормально - вы не идете вразрез с духом "агрегата без поведения". Проблема в том, что структура больше не является типом POD, поэтому стандарт дает меньше гарантий относительно ее поведения, и ее нельзя сохранить в союз.

Вы рассматривали это вместо этого?

Position p = {12.34, 56.78};

С тех пор, как я опубликовал этот вопрос, меня укусили за определение конструкторов для моих агрегатов. Используя мой пример положения выше, GCC жалуется, когда я делаю это:

const Position pos[] = 
{
    {12.34, 56.78},
    {23.45, 67.89},
};

предупреждение: расширенные списки инициализаторов доступны только с -std= C++0x или -std=gnu++0x|

Вместо этого я должен сделать это:

const Position pos[] =
{
    Position(12.34, 56.78),
    Position(23.45, 67.89)
};

С этим решением я беспокоюсь, что во встроенных системах моя постоянная таблица не будет храниться во флэш /ROM.

РЕДАКТИРОВАТЬ:

Я попытался удалить конструктор Position, и массив pos действительно переместился из.bss в сегмент.rodata.

Другие вопросы по тегам