Глава 15 Разные функции

15.0. Введение

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

15.1. Применение указателей функций для их обратного вызова

Проблема

Планируется использование некоторой функции

func1
, которая на этапе выполнения должна вызывать другую функцию
func2
. Однако по той или иной причине нельзя внутри функции
func1
жестко закодировать имя функции
func2
. Возможно, имя функции
func2
неизвестно на этапе компиляции, или
func1
относится к программному интерфейсу независимого разработчика, и она не может быть изменена и перекомпилирована В любом случае вам придется воспользоваться функцией обратного вызова (callback function).

Решение

При использовании указанных выше функций объявите

func1
с указателем на функцию в качестве своего аргумента и передайте ей адрес
func2
на этапе выполнения. Используйте
typedef
, чтобы программа легче читалась и отлаживалась. Пример 15.1 показывает, как можно реализовать функцию обратного вызова, используя указатель на функцию.

Пример 15.1. Функция обратного вызова

#include 


// Пример функции обратного вызова

bool updateProgress(int pct) {

 std::cout << pct << "% complete...\n";

 return(true);

}


// Этот typedef делает программный код более понятным

typedef bool (*FuncPtrBoolInt)(int);


// Функция, которая выполняется достаточно длительное время

void longOperation(FuncPtrBoolInt f) {

 for (long l=0; l < 100000000; l++)

 if (l % 10000000 == 0)

 f(l/1000000);

}


int main() {

 longOperation(updateProgress); // нормально

}

Обсуждение

В ситуации, которая показана в примере 15.1, применение указателя на функцию является хорошим решением, если

UpdateProgress
и
longOperation
ничего не должны знать друг о друге. Например, функцию, которая обновляет индикатор состояния процесса в диалоговом окне пользовательского интерфейса (user interface — UI), в окне консольного режима или где-то еще, не заботит контекст, в котором она вызывается. Аналогично функция
longOperation
может быть частью некоторого программного интерфейса загрузки данных, которого не заботит место вызова: из графического UI, из окна консольного режима или из фонового процесса.

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

typedef
. Оператор
typedef
— ваш помощник в тех случаях, когда приходится иметь дело с указателями функций, потому что они имеют не очень привлекательный синтаксис. Рассмотрим, как обычно объявляется такой указатель на примере переменной
f
, которая содержит адрес функции, принимающей единственный аргумент целого типа и возвращающей значения типа
boolean
. Это может выглядеть следующим образом

bool (*f)(int); // f - имя переменной

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

vector
таких указателей?

vector vf;

Или их массив?

bool (*af[10])(int);

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

Итак, в примере 15.1 я использовал следующий

typedef
.

typedef bool (*FuncPtrBoolInt)(int);

Сделав это, я могу свободно объявлять указатели функций с сигнатурой, возвращающей значение

bool
и принимающей единственный аргумент, как это я бы делал для параметра любого другого типа, например.

void longOperation(FuncPtrBoolInt f) { // ...

Теперь все, что надо сделать в

longOperation
, — это вызвать
f
, как если бы это была любая обычная функция.

f(l/1000000);

Таким образам, здесь

f
может быть любой функцией, которая принимает аргумент целого типа и возвращает
bool
. Предположим, что в вызывающей функции
longOperation
не требуется обеспечивать продвижение индикатора состояния процесса. Тогда ей можно передать указатель на функцию без операций.

bool whoCares(int i) {return(true);}

//...

longOperation(whoCares);

Более важно то, что выбор функции, передаваемой

longOperation
, может осуществляться динамически на этапе выполнения.

15.2. Применение указателей для членов класса

Проблема

Требуется обеспечить адресную ссылку на данное-член или на функцию-член.

Решение

Используйте имя класса и оператор области видимости (

::
) со звездочкой для правильного квалифицирования имени. Пример 15.2 показывает, как это можно сделать.

Пример 15.2. Получение указателя на член класса

#include 

#include 


class MyClass {

public:

 MyClass() : ival_(0), sval_("foo") {}

 ~MyClass() {}

 void incr() {++ival_;}

 void decr() {ival_--;}

private:

 std::string sval_;

 int ival_;

};


int main() {

 MyClass obj;

 int MyClass::* mpi = &MyClass::ival_;     // Указатели на

 std::string MyClass::* mps = &MyClass::sval_; // данные-члены

 void (MyClass::*mpf)(); // Указатель на функцию-член, у которой

             // нет параметров и которая возвращает void

 void (*pf)(); // Обычный указатель на функцию

 int* pi = &obj.ival_; // int-указатель, ссылающийся на переменную-член

            // типа int, - все нормально.

 mpf = &MyClass::incr; // Указатель на функцию-член. Вы не можете

            // записать это значение в поток. Посмотрите в

            // отладчике, как это значение выглядит.

 pf = &MyClass::incr; // Ошибка: &MyClass::inc не является экземпляром

            // функции

 std::cout << "mpi = " << mpi << '\n';

 std::cout << "mps = " << mps << '\n';

 std::cout << "pi = " << pi << '\n';

 std::cout << "*pi = " << *pi << '\n';

 obj.*mpi = 5;

 obj.*mps = "bar";

 (obj.*mpf)(); // теперь obj.ival_ равно 6

 std::cout << "obj.ival_ = " << obj.ival_ << '\n';

 std::cout << "obj.sval_ = " << obj.sval_ << '\n';

}

Обсуждение

Указатели на члены класса выглядят и работают иначе, чем обычные указатели. Прежде всего, они имеют «смешной» синтаксис (не вызывающий смех, но странный). Рассмотрим следующую строку из примера 15.2.

int MyClass::* mpi = &MyClass::ival_;

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

MyClass
. Две вещи отнимают это объявление от обычного
int*
. Во-первых, вам приходится вставлять имя класса и оператор области видимости между типом данного и звездочкой. Во-вторых, при выполнении операции присваивания этому указателю на самом деле не назначается какой то определенный адрес памяти. Значение
&MyClass::ival_
не является каким-то конкретным значением, содержащимся в памяти; оно ссылается на имя класса, а не на имя объекта, но тогда что же это такое на самом деле? Можно представить это значение как смешение данного-члена относительно начального адреса объекта.

Переменная

mpi
должна использоваться совместно с экземпляром класса, к которому она применяется. Немного ниже в примере 15.2 располагается следующая строка, которая использует
mpi
для присваивания целого числа значению, на которое ссылается указатель
mpi
.

obj.*mpi = 5;

obj
является экземпляром класса
MyClass
. Ссылка на член с использованием точки (или
->
, если у вас имеется указатель на
obj
) и разыменование
mpi
позволяют вам получить ссылку на
obj.ival_
.

Указатели на функции-члены действуют фактически так же. В примере 15.2 объявляется указатель на функцию-член

MyClass
, которая возвращает
void
и не имеет аргументов.

void (MyClass::*mpf)();

Ему можно присвоить значение с помощью оператора адресации.

mpf = &MyClass::incr;

Для вызова функции заключите основное выражение в скобки, чтобы компилятор понял ваши намерения, например:

(obj.*mpf)();

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

int* pi = &obj.ival_;

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

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

MyClass::incr
(т.е. он возвращает
void
и не имеет аргументов).

void (*pf)();

Теперь попытайтесь присвоить этому указателю адрес функции-члена.

pf = &MyClass::incr; // He получится

pf = &obj.incr;    // И это не пройдет

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

См. также

Рецепт 15.1.

15.3. Обеспечение невозможности модификации аргумента в функции

Проблема

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

Решение

Для предотвращения изменения аргументов вашей функцией объявите ее аргументы с ключевым словом

const
. Короткий пример 15.3 показывает, как это можно сделать.

Пример 15.3. Гарантия невозможности модификации аргументов

#include 

#include 


void concat(const std::string& s1, // Аргументы объявлены как константное,

 const std::string& s2,       // поэтому не могут быть изменены

 std::string& out) {

 out = s1 + s2;

}


int main() {

 std::string s1 = "Cabo ";

 std::string s2 = "Wabo";

 std::string s3;

 concat(s1, s2, s3);

 std::cout << "s1 = " << s1 << '\n';

 std::cout << "s2 = " << s2 << '\n';

 std::cout << "s3 = " << s3 << '\n';

}

Обсуждение

В примере 15.3 продемонстрировано прямое использование ключевого слова

const
. Существует две причины объявления параметров вашей функции с этим ключевым словом, когда вы не планируете их изменять. Во-первых, этим вы сообщаете о своих намерениях читателям вашего программного кода. Объявляя параметр как
const
, вы фактически говорите, что он является входным параметром. Это позволяет пользователям вашей функции писать программный код в расчете на то, что эти значения не будут изменены. Во-вторых, это позволяет компилятору запретить любые модифицирующие операции на тот случай, если вы случайно их используете. Рассмотрим небезопасную версию
concat
из примера 15 3.

void concatUnsafe(std::string& s1,

 std::string& s2 std::string& out) {

 out = s1 += s2; // Ну вот, записано значение в s1

}

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

+=
вместо
+
. В результате при вызове
concatUnsafe
будут модифицированы аргументы
out
и
s1
, что может оказаться сюрпризом для пользователя, который едва ли рассчитывает на модификацию одной из исходных строк.

Спасти может

const
. Создайте новую функцию
concatSafe
, объявите переменные константными, как показано в примере 15.3, и функция не будет откомпилирована.

void concatSafe(const std::string& s1,

 const std::string& s2, std::string& out) {

 out = s1 += s2; // Теперь вы получите ошибку компиляции

}

concatSafе
гарантирует неизменяемость значений в
s1
и
s2
. Эта функция делает еще кое-что: она позволяет пользователю передавать константные аргументы. Например, программный код, выполняющий конкатенацию строк, мог бы выглядеть следующим образом.

void myFunc(const std::string& s) { // Обратите внимание, что s является

                  // константной переменной

 std::string dest;

 std::string tmp = "foo";

 concatUnsafe(s, tmp, dest); // Ошибка: s - константная переменная

               // Выполнить какие-то действия с dest...

}

В данном случае функция

myFunc
не будет откомпилирована, потому что
concatUnsafe
не обеспечивает
const
'антность
myFunc
.
myFunc
гарантирует внешнему миру, что она не будет модифицировать содержимое
s
, т.е. все действия с
s
внутри тела
myFunc
не должны нарушать это обещание. Конечно, вы можете обойти это ограничение, используя оператор
const_cast
и тем самым освобождаясь от константности, но такой подход ненадежен, и его следует избегать. В этой ситуации
concatSafe
будет компилироваться и выполняться нормально.

Указатели вносят темные штрихи в розовую картину

const
. Когда вы объявляете переменную-указатель как параметр, вы имеет дело с двумя объектами: самим адресом и то, на что ссылается этот адрес. C++ позволяет использовать
const
для ограничения действий по отношению к обоим объектам. Рассмотрим еще одну функцию конкатенации, которая использует указатели.

void concatUnsafePtr(std::string* ps1,

 std::string* ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Здесь такая же проблема, как в примере с

concatUnsafe
, описанном ранее. Добавьте
const
для гарантии невозможности обновления исходных строк.

void concatSaferPtr(const std::string* ps1,

 const std::string* ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Отлично, теперь вы не можете изменить

*ps1
и
*ps2
. Но вы по-прежнему можете изменить
ps1
и
ps2
, или, другими словами, используя их, вы можете сослаться на какую-нибудь другую строку, изменяя значение указателя, но не значение, на которое он ссылается. Ничто не может помешать вам, например, сделать следующее.

void concatSaferPtr(const std:string* ps1,

 const std::string* ps2, std::string* pout) {

 ps1 = pout; // Ух!

 *pout = *ps1 + *ps2;

}

Предотвратить подобные ошибки можно с помощью еще одного

const
.

void concatSafestPtr(const std::string* const ps1,

 const std::string* const ps2, std::string* pout) {

 *pout = *ps1 + *ps2;

}

Применение

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

См. также

Рецепт 15.4.

15.4. Обеспечение невозможности модификации своих объектов в функции-члене

Проблема

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

Решение

Поместите ключевое слово

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

Пример 15.4. Объявление функции-члена константной

#include 

#include 


class RecordSet {

public:

 bool getFieldVal(int i, std::string& s) const;

 // ...

};


bool RecordSet::getFieldVal(int i, std::string& s) const {

 // Здесь нельзя модифицировать никакие неизменяемые

 // данные-члены (см. обсуждение)

}


void displayRecords(const RecordSet& rs) {

 // Здесь вы можете вызывать только константные функции-члены

 // для rs

}

Обсуждение

Добавление концевого

const
в объявление члена и в его определение заставляет компилятор более внимательно отнестись к тому, что делается с объектом внутри тела члена. Константным функциям-членам не разрешается выполнять неконстантные операции с данными-членами. Если такие операции присутствуют, компиляция завершится неудачно. Например, если бы в
RecordSet::getFieldVal
я обновил счетчик-член, эта функция не была бы откомпилирована (в предположении, что
getFieldCount_
является переменной-членом класса
RecordSet
).

bool RecordSet::getFieldVal(int i, std::string& s) const {

 ++getFieldCount_; // Ошибка: константная функция-член не может

          // модифицировать переменную-член

          // ...

}

Это может также помочь обнаружить более тонкие ошибки, подобно тому, что делает

const
в роли квалификатора переменной (см. рецепт 15.3). Рассмотрим следующую глупую ошибку.

bool RecordSet::getFieldVal(int i, std::string& s) const {

 fieldArray_[i] = s; // Ой, я не это имел в виду

 // ...

}

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

В классе

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

void RecordSet::gotoNextPecord() const {

 if (curIndex_ >= 0 && curIndex_ < numRecords_-1)

  ++curIndex_;

}


void RecordSet::gotoPrevRecord() const {

 if (curIndex_ > 0)

  --curIndex_;

}

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

RecordSet
не смогут перемещаться по объекту
const RecordSet
. Это исключение из правил работы с константными функциями-членами является вполне разумным, поэтому C++ имеет механизм его поддержки: ключевое слово
mutable
.

Для того чтобы

curIndex_
можно было обновлять в константной функции-члене, объявите ее с ключевым словом mutable в объявлении класса.

mutable int curIndex_;

Это позволит вам модифицировать

curIndex_
в любом месте. Однако этой возможностью следует пользоваться разумно, поскольку это действует на вашу функцию так, как будто она становится с этого момента неконстантной.

Применение ключевого слова

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

15.5. Написание оператора, не являющегося функцией-членом

Проблема

Необходимо написать бинарный оператор, и вы не можете или не хотите сделать его функцией-членом класса.

Решение

Используйте ключевое слово

operator
, временную переменную и конструктор копирования для выполнения основной работы и возвратите временный объект. В примере 15.5 приводится простой оператор конкатенации строк для пользовательского класса
String
.

Пример 15.5. Конкатенация с использованием оператора не члена

#include 

#include 


class String { // Предположим, что объявление класса String содержит,

        // по крайней мере, все, что указанно ниже

public:

 String();

 String(const char* p);

 String(const String& orig);

 ~String() {delete buf_;}

 String& append(const String& s);

 size_t length() const;

 const char* data() const;

 String& operator=(const String& orig);

 // ...

};


String operator+(const String& lhs, const String& rhs) {

 String tmp(lhs); // Сконструировать временный объект с помощью

          // конструктора копирования

 tmp.append(rhs); // Использовать функцию-член для выполнения реальной

          // работы

 return(tmp); // Возвратить временный объект

}


int main() {

 String s1("banana ");

 String s2("rancher");

 String s3, s4, s5, s6;

 s3 = s1 + s2;      // Работает хорошо, но с сюрпризами

 s4 = s1 + "rama";    // Автоматически конструируется "rama", используя

             // конструктор String(const char*)

 s5 = "ham " + s2;    // Круто, то же самое можно делать даже

 s6 = s1 + "rama " + s2; // с другим операндом

 std::cout << "s3 = " << s3.data() << '\n';

 std::cout << "s4 = " << s4.data() << '\n';

 std::cout << "s5 = " << s5.data() << '\n';

 std::cout << "s6 = " << s6.data() << '\n';

}

Обсуждение

Независимый оператор объявляется и определяется подобно оператору функции-члена. В примере 15.5 я мог бы реализовать

operator+
как функцию-член, объявляя ее следующим образом.

String operator+(const String& rhs);

В большинстве случаев это будет работать одинаково, независимо от того, определяется ли

operator+
как функция-член или нет, однако существует, по крайней мере, две причины, по которым желательно реализовать его не как функцию-член. Первая причина концептуальная, имеет ли смысл иметь оператор, который возвращает новый, отличный от других объект?
operator+
, реализованный как функция-член, не проверяет и не изменяет состояние объекта. Это служебная функция общего назначения, которая в данном случае работает со строками типа
String
и, следовательно, не должна являться функцией членом.

Вторая причина техническая. При использовании оператора-члена вы не сможете выполнить следующую операцию (из приведенного выше примера).

s5 = "ham " + s2;

Это не сработает, потому что символьная строка не имеет

operator+
, который принимает
String
в качестве параметра. С другой стороны, если вы определили независимый
operator+
, который принимает два параметра типа
String
, ваш компилятор проверит наличие в классе
String
конструктора, принимающего
const char*
в качестве аргумента (или любой другой тип, который вы используете совместно с
String
), и сконструирует временный объект на этапе выполнения. Поэтому приведенная выше строка эквивалентна следующей.

s5 = String("ham ") + s2;

Компилятор позволяет вам немного сэкономить ваши действия и не вводить несколько символов за счет поиска и вызова соответствующего конструктора.

Перегрузка операторов сдвига потоков влево и вправо (

<<
и
>>
) также требует применения операторов не-членов. Например, для записи нового объекта в поток, используя сдвиг влево, вам придется следующим образом объявить
operator<<
:

ostream& operator<<(ostream& str, const MyClass& obj);

Конечно, вы можете создать подкласс одного из классов потока стандартной библиотеки и добавить все необходимые вам операторы сдвига влево, но будет ли такое решение действительно удачным? При таком решении только тот программный код, который использует ваш новый класс потока, сможет записывать в него объекты вашего специального класса. Если вы используете независимый оператор, любой программный код в том же самом пространстве имен сможет без проблем записать ваш объект в

ostream
(или считать его из
istream
).

15.6. Инициализация последовательности значениями, разделяемыми запятыми

Проблема

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

Решение

При инициализации стандартных последовательностей (таких как

vector
и
list
) можно использовать синтаксис с запятыми, определяя вспомогательный класс и перегружая оператор запятой, как это продемонстрировано в примере 15.6.

Пример 15.6. Вспомогательные классы для инициализации стандартных последовательностей с применением синтаксиса с запятыми

#include 

#include 

#include 

#include 


using namespace std;


template

struct comma helper {

 typedef typename Seq_T::value_type value_type;

 explicit comma_helper(Seq_T& x) : m(x) {}

 comma_helper& operator=(const value_type& x) {

  m.clear();

  return operator+=(x);

 }

 comma_helper& operator+=(const value_type& x) {

  m.push_back(x);

  return *this;

 }

 Seq_T& m;

};


template

comma_helper initialize(Seq_T& x) {

 return comma_helper(x);

}


template

comma_helper& operator,(comma_helper& h, Scalar_T x) {

 h += x;

 return h;

}


int main() {

 vector v;

 int a = 2;

 int b = 5;

 initialize(v) = 0, 1, 1, a, 3, b, 8, 13;

 cout << v[3] << endl; // выдает 2

 system("pause");

 return EXIT_SUCCESS;

}

Обсуждение

Часто стандартные последовательности инициализируются путем вызова несколько раз функции-члена

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

Возможно, вы и не знали, что запятая является оператором, который можно переопределять. Здесь вы не одиноки — этот факт не является общеизвестным. Оператор запятой было разрешено перегружать почти только ради решения этой задачи.

В решении используется вспомогательная функция

initialize
, которая возвращает шаблон вспомогательной функции
comma_helper
. Этот шаблон содержит ссылку на последовательность и перегруженные операторы
operator,
,
operator=
и
operator+=
.

Такое решение требует, чтобы я определил отдельную вспомогательную функцию из-за особенностей восприятия компилятором оператора

v = 1, 1, 2, ...;
. Компилятор рассматривает
v = 1
как недопустимое подвыражение, потому что в стандартных последовательностях не поддерживается оператор присваивания единственного значения. Функция
initialize
конструирует соответствующий объект
comma_helper
, который может хранить последовательность, используемую в перегруженном операторе присваивания и запятой.

Оператор запятой (comma operator), называемый также оператором последовательности (sequencing operator), по умолчанию рассматривает выражения слева направо, и в результате получается значение и тип самого правого значения. Однако при перегрузке

operator
принимает новый смысл и теряет первоначальную семантику. Здесь возникает один тонкий момент — оценка параметров слева направо теперь не гарантируется, и результат выполнения программного кода, приведенного в примере 15.7, может оказаться неожиданным.

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

int prompt_user() {

 cout << "give me an integer ... ";

 cin >> n;

 return n;

}


void f() {

 vector v;

 // Следующий оператор может инициализировать v в неправильной

 // последовательности

 intialize(v) = prompt_user(), prompt_user();

}

В правильном варианте функции

f
каждый вызов
prompt_user
должен был бы выполняться в отдельном операторе.

Библиотека Boost Assign, написанная Торстеном Оттосеном (Thorsten Ottosen), кроме других форм инициализации стандартных коллекций поддерживает также более сложную форму инициализации списком с запятыми. Эта библиотека доступна на сайте http://www.boost.org.

Загрузка...