extern и forward declaration
От: musix Россия  
Дата: 23.05.10 15:36
Оценка:
Всем привет!
Подскажите можно ли делать в C++ предварительное определение переменных и объектов классов? Вот например такие классы:
class B;

class A 
{
public:
    A(B* rb){}
};

class B
{
public:
    B(A* ra){}
};


Можно создать глобальные объекты этих классов:
extern B b;
A a(&b);
B b(&a);

void main(void)
{
}


но не получается создать локальные:

void main(void)
{
    extern B b;
    A a(&b);
    B b(&a);
}


Для того, чтобы сделать объявление функции можно написать либо
extern void f(void);
либо просто
void f(void);


потому, что объявление функции и так можно отличить от определения, в отличие от переменных. А для объявления переменной extern обязателен — такое у меня было предположение. Но поэкспериментировав я выяснил, что поторопился с предположением.

Вобщем запутался я немного. Можно ли делать объявления переменных и объектов классов или нет?

Вроде получается, что для глобальных можно:

extern int i;
int * pi = &i;
int i = (int)pi;

void main(void)
{
}

а для локальных нет:

void main(void)
{
   extern int i;
   int * pi = &i;
   int i = (int)pi; // Ошибка: переопределение i
}


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

P.S.: MSDN читал на данную тему, но все равно не все понял ((
Re: extern и forward declaration
От: MasterZiv СССР  
Дата: 23.05.10 17:02
Оценка:
musix пишет:

> Можно создать глобальные объекты этих классов:

>
> extern B b;
> A a(&b);
> B b(&a);

Для того, чтобы создать объект, его класс должен
быть полностью определён. Тут ты НЕ СОЗДАЁШ никаких
глобальных объектов.

extern B b;

говорит компилятору буквально следующее:
где-то в моей программе будет объект класса B с именем
b, на который я буду ссылаться в данном модуле компиляции
и который будет определён в каком-то другом модуле компиляции,
неизвестном.

> extern void f(void);

> либо просто
> void f(void);

это одно и то же вообще.

> Похоже что ответ на мой вопрос отрицательный, но тогда почему? Как мне

> создать локальные объекты классов A и B?

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

Если класс неполностью определён (как class Foo ты сможешь только
использовать переменные типа "указатель на этот класс" или "ссылка на
этот класс" или подобные производные, но при этом разименовывать
(иметь доступ к мемберам через эти ссылки) уже будет нельзя,
нужно полное определение класса.
Posted via RSDN NNTP Server 2.1 beta
Re[2]: extern и forward declaration
От: musix Россия  
Дата: 24.05.10 08:55
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>Для того, чтобы создать объект, его класс должен

MZ>быть полностью определён. Тут ты НЕ СОЗДАЁШ никаких
MZ>глобальных объектов.

Как это? А что же я по-твоему создаю?

MZ>extern B b;


MZ>говорит компилятору буквально следующее:

MZ>где-то в моей программе будет объект класса B с именем
MZ>b, на который я буду ссылаться в данном модуле компиляции
MZ>и который будет определён в каком-то другом модуле компиляции,
MZ>неизвестном.

Это не совсем верно — объект b может быть определен в этом же модуле компиляции.

>> extern void f(void);

>> либо просто
>> void f(void);

MZ>это одно и то же вообще.


Я об этом и пишу. Не понятно пишу что ли?

MZ>ЧТобы создать объект класса надо иметь сам класс полностью определённым,

MZ>не зависимо какая переменная для этого используется: глобальная, локальная,
MZ>статическая -- любая.

MZ>Если класс неполностью определён (как class Foo ты сможешь только

MZ>использовать переменные типа "указатель на этот класс" или "ссылка на
MZ>этот класс" или подобные производные, но при этом разименовывать
MZ>(иметь доступ к мемберам через эти ссылки) уже будет нельзя,
MZ>нужно полное определение класса.

Какой класс Foo?? Не создаю я никаких указателей и ссылок! Вот тебе, пожалуйста,
доступ доступ к мемберам:

class B;

class A 
{
public:
    A(B* rb){m_x=123;}
    int GetX() const {return m_x;}
private:
    int m_x;
};

class B
{
public:
    B(A* ra){}
};

extern B b;
A a(&b);
B b(&a);

void main(void)
{
    int x = a.GetX();
}


Вобщем за попытку ответить спасибо конечно, но сам-то понял что написал??
Re: extern и forward declaration
От: Кодт Россия  
Дата: 24.05.10 09:49
Оценка:
Здравствуйте, musix, Вы писали:

M>Подскажите можно ли делать в C++ предварительное определение переменных и объектов классов?


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

forward declaration — вводит имя класса, но не его содержимое.
Про этот класс неизвестно ничего, даже его sizeof, поэтому объект этого класса нельзя определить.
Указатель же и ссылка на предобъявленный класс — это вполне работоспособные типы.
Собственно, поэтому они и используются для перекрёстных ссылок.

Объявление глобальной переменной (extern) — это, фактически, ссылка на неё.
Поэтому объявление переменной предобъявленного класса возможно.
Для определения же — компилятору требуется знать и размер, и конструктор, и деструктор.

extern относится только к глобальным переменным.
Это очевидно: "extern" = "внешний", т.е. "где-то, возможно, даже не в данном .c или .cpp файле" — пусть линкер потом найдёт среди .obj и .lib и подставит адрес.

Объявление локальной переменной невозможно. Только определение (хотя бы и без инициализации, если это POD-тип).
А определение, как я уже сказал, требует полностью определённый тип.

Запись
void foo()
{
  extern Bar bar; // объявление глобальной переменной
  ...
  Bar bar; // объявление локальной переменной в той же области видимости
  Buz bar; // ничем не лучше, чем объявление ещё одной локальной переменной с тем же именем
           // вот компилятор и ругается
}
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Re[2]: extern и forward declaration
От: musix Россия  
Дата: 24.05.10 13:07
Оценка:
Частично понятно, а частично нет((

1. Мне не понятно почему вы говорите про неполностью определенные типы? Какой тип у меня не полностью определен?

Но ладно, я и без классов могу сформулировать свой вопрос. А именно:

2. Вот два схожих примера:

example_1.cpp:

extern int i; // (1)
int * p = &i; // (2)
int i = (int)p; // (3)
void main(void) {}


и

example_2.cpp:

extern int g(); // (1)
int f(void) {return (int)&g;} // (2)
int g(void) {return (int)&f;} // (3)
void main(void) {}


В обоих случаях удалось сначала ОБЪЯВИТЬ сущность (1), потом ИСПОЛЬЗОВАТЬ (2) и только потом ОПРЕДЕЛИТЬ (3). Обстоятельство которе меня сбивает с толку — первое действие в обоих случаях выполняется с использованием ключевого слова extern.

Мне не понятно есть ли в языке возможность проделать такой трюк с локальными переменными. Т.е. вот так вот:

question.cpp

void main(void)
{
   declare int i; // объявление int i; (1)
   int * p = &i; // (2)
   int i = (int)p; // (3)
}


a) Если такое можно сделать, то как?
b) Если нельзя, то почему нельзя? Почему для глобальных переменных можно, а для локальных нельзя?
Re[3]: extern и forward declaration
От: Кодт Россия  
Дата: 24.05.10 14:01
Оценка:
Здравствуйте, musix, Вы писали:

M>1. Мне не понятно почему вы говорите про неполностью определенные типы? Какой тип у меня не полностью определен?

class B; // вот он появился
class A
{
  ... B* ... // а вот его используют
};
class B
{
  ... A* ...
};


M>Но ладно, я и без классов могу сформулировать свой вопрос. А именно:


M>2. Вот два схожих примера:

M>
extern int i; // (1)
M>int * p = &i; // (2)
M>int i = (int)p; // (3)
M>void main(void) {}


Здесь (1) объявление, (2) использование и (3) определение глобальной переменной.

M>
extern int g(); // (1)
M>int f(void) {return (int)&g;} // (2)
M>int g(void) {return (int)&f;} // (3)
M>void main(void) {}


Здесь (1) объявление, причём слово extern необязательно. Все функции, если они не static, считаются extern.

M>В обоих случаях удалось сначала ОБЪЯВИТЬ сущность (1), потом ИСПОЛЬЗОВАТЬ (2) и только потом ОПРЕДЕЛИТЬ (3). Обстоятельство которе меня сбивает с толку — первое действие в обоих случаях выполняется с использованием ключевого слова extern.


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

Компилятор отличает
— объявление/определение глобальной переменной — по наличию слова extern И отсутствию инициализации
— объявление/определение глобальной функции — по отсутствию тела {}

К сожалению, это тяжкое синтаксическое наследие, когда кейворд значит не совсем то, что значит.
Например, слово inline на самом деле означает (в терминах MS) __declspec(selectany).

M>Мне не понятно есть ли в языке возможность проделать такой трюк с локальными переменными.

M>a) Если такое можно сделать, то как?
M>b) Если нельзя, то почему нельзя? Почему для глобальных переменных можно, а для локальных нельзя?

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

Но есть пара нюансов
1) для типов с тривиальным конструктором (в частности, для POD) можно сперва определить (создать), а уже потом инициализировать
struct Foo { int x, y, z; };
void bar()
{
  Foo f; // создали на стеке, ещё не инициализировали
  int* p = &f.x; // валидный указатель
  f.y = (int)p; // инициализировали f.y
  // f.x и f.z всё ещё не инициализированы, да и фиг с ними
  *p = 123; // инициализировали f.x
  cout << f.x; // ok
  cout << f.y; // ok
  cout << f.z; // мягкое UB - чтение мусора
}

2) ссылка на переменную доступна в точке её определения, до конструктора — хотя ещё не валидна
struct Foo
{
  Foo* p;
  Foo(Foo* p) : p(p)
  {
    cout << "ctor " << (void*)p << " : "
                    << (void*)p->p // будет UB - обращение к несконструированному объекту
                    << endl; 
    boom(); // ok
    //p->boom(); // катастрофа: у того объекта vfptr ещё не установлен
  }
  virtual void boom() {}
};

int main()
{
  int x = (int)&x;
  int y[3] = { y[1]+1, y[2]+10, y[0]+100 };
    // массив заполнен какой-то фигнёй!
    // мягкое UB - чтение неинициализированных значений

  Foo f = Foo(Foo(Foo(&f)));
    // для пущего ужаса, сделал несколько конструкторов копирования
  cout << (void*)&f << " == " << (void*)f.p;
}
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Re[3]: extern и forward declaration
От: MasterZiv СССР  
Дата: 24.05.10 18:42
Оценка:
musix пишет:

> MZ>Для того, чтобы создать объект, его класс должен

> MZ>быть полностью определён. Тут ты НЕ СОЗДАЁШ никаких
> MZ>глобальных объектов.
>
> Как это? А что же я по-твоему создаю?

Ничего.
Posted via RSDN NNTP Server 2.1 beta
Re[3]: extern и forward declaration
От: MasterZiv СССР  
Дата: 24.05.10 19:04
Оценка:
musix пишет:

> Вобщем за попытку ответить спасибо конечно, но сам-то понял что написал??


Ну, как мог, объяснил.
Posted via RSDN NNTP Server 2.1 beta
Re[4]: extern и forward declaration
От: musix Россия  
Дата: 25.05.10 09:59
Оценка: +1
Кажется я начинаю постигать суть вещей!

1. Ключевое слово extern можно использовать для того чтобы сделать объявление глобальной переменной или объекта класса.

Сейчас объясню почему мне так кажется. Рассмотрим вышеупомянутые классы A и B:
class B;

class A 
{
public:
    A(B* pb){}
};

class B
{
public:
    B(A* pa){}
};


Можно создать глобальные объекты этих классов:

extern B b; //(1) объявление
A a(&b); // (2) использование
B b(&a); // (3) определение
void main(void) {}


Причина в том, что память для a и для b будет выделена ДО того как будет вызван конструктор a или b. Т.е. в момент вызова конструктора для a или конструктора для b память уже будет выделена для обоих объектов.

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

Отсюда сразу получаем, что объекты классов A и B можно создать локальными опосредованным способом — нужно чтобы память для них ОБОИХ была выделена до вызова конструкторов (т.е. по аналогии со случаем глобальных переменных). Например можно поступить так:

class AB
{
public:
    AB () : m_a(&m_b), m_b(&m_a) {}
private:
    A m_a;
    B m_b;
};

void main(void)
{
    AB ab;
}


Так ведь? Или все же я чего-то неправильно понимаю?
Re[5]: extern и forward declaration
От: Кодт Россия  
Дата: 25.05.10 10:22
Оценка:
Здравствуйте, musix, Вы писали:

M>Отсюда сразу получаем, что объекты классов A и B можно создать локальными опосредованным способом — нужно чтобы память для них ОБОИХ была выделена до вызова конструкторов (т.е. по аналогии со случаем глобальных переменных). Например можно поступить так:

M>class AB
M>{
M>public:
M>    AB () : m_a(&m_b), m_b(&m_a) {}
M>private:
M>    A m_a;
M>    B m_b;
M>};

M>void main(void)
M>{
M>    AB ab;
M>}

M>Так ведь? Или все же я чего-то неправильно понимаю?

Ах, вот что тебе было нужно! Так сразу бы и сказал А то заострил внимание на синтаксисе.

Да, такой подход возможен. Больше того, можно использовать локальные классы
int main() // void main() - это Си
{
  struct AB // struct вместо class - экономим на public:
  {
    A a;
    B b;
    AB() : a(&b), b(&a) {}
  } ab;
}
http://files.rsdn.org/4783/catsmiley.gif Перекуём баги на фичи!
Re[6]: extern и forward declaration
От: musix Россия  
Дата: 26.05.10 10:36
Оценка:
К>Ах, вот что тебе было нужно! Так сразу бы и сказал А то заострил внимание на синтаксисе.
Не, в первую очередь все же меня интересовало есть ли в языке возможность объявлять локальные переменные не определяя их сразу Т.е. все же больше про синтакис языка вопрос был. Вроде понятно теперь.

Спасибо за ответы!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.