tp.priv = 1024;
}
У объединения не бывает статических членов или членов, являющихся ссылками. Его членом не может быть класс, имеющий конструктор, деструктор или копирующий оператор присваивания. Например:
union illegal_members {
Screen s; // ошибка: есть конструктор
Screen *ps; // правильно
static int is; // ошибка: статический член
int // ошибка: член-ссылка
};
Для объединения разрешается определять функции-члены, включая конструкторы и деструкторы:
union TokenValue {
public:
TokenValue(int ix) : _ival(ix) { }
TokenValue(char ch) : _cval(ch) { }
// ...
int ival() { return _ival; }
char cval() { return _cval; }
private:
int _ival;
char _cval;
// ...
};
int main() {
TokenValue tp(10);
int ix = tp.ival();
//...
}
Вот пример работы объединения TokenValue:
enum TokenKind ( ID, Constant /* и другие типы лексем */ }
class Token {
public:
TokenKind tok;
TokenValue val;
};
Объект типа Token можно использовать так:
int lex() {
Token curToken;
char *curString;
int curIval;
// ...
case ID: // идентификатор
curToken.tok = ID;
curToken.val._sval = curString;
break;
case Constant: // целая константа
curToken.tok = Constant;
curToken.val._ival = curIval;
break;
// ... и т.д.
}
Опасность, связанная с применением объединения, заключается в том, что можно случайно извлечь хранящееся в нем значение, пользуясь не тем членом. Например, если в последний раз значение присваивалось _ival, то вряд ли понадобится значение, оказавшееся в _sval. Это, по всей вероятности, приведет к ошибке в программе.
Чтобы защититься от подобного рода ошибок, следует создать дополнительный объект, дискриминант объединения, определяющий тип значения, которое в данный момент хранится в объединении. В классе Token роль такого объекта играет член tok:
char *idVal;
// проверить значение дискриминанта перед тем, как обращаться к sval
if ( curToken.tok == ID )
idVal = curToken.val._sval;
При работе с объединением, являющимся членом класса, полезно иметь набор функций для каждого хранящегося в объединении типа данных:
#include
// функции доступа к члену объединения sval
string Token::sval() {
assert( tok==ID );
return val._sval;
}
Имя в определении объединения задавать необязательно. Если оно не используется в программе как имя типа для объявления других объектов, его можно опустить. Например, следующее определение объединения Token эквивалентно приведенному выше, но без указания имени:
class Token {
public:
TokenKind tok;
// имя типа объединения опущено
union {
char _cval;
int _ival;
char *_sval;
double _dval;
} val;
};
Существует анонимное объединение – объединение без имени, за которым не следует определение объекта. Вот, например, определение класса Token, содержащее анонимное объединение:
class Token {
public:
TokenKind tok;
// анонимное объединение
union {
char _cval;
int _ival;
char *_sval;
double _dval;
};
};
К данным-членам анонимного объединения можно напрямую обращаться в той области видимости, в которой оно определено. Перепишем функцию lex(), используя предыдущее определение:
int lex() {
Token curToken;
char *curString;
int curIval;
// ... выяснить, что находится в лексеме
// ... затем установить curToken
case ID:
curToken.tok = ID;
curToken._sval = curString;
break;
case Constant: // целая константа
curToken.tok = Constant;
curToken._ival = curIval;
break;
// ... и т.д.
}