Форум программистов, компьютерный форум CyberForum.ru

Можно ли объявлять объекты в заголовочном файле? - C++

Восстановить пароль Регистрация
 
Рейтинг: Рейтинг темы: голосов - 10, средняя оценка - 4.80
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
11.08.2014, 19:16     Можно ли объявлять объекты в заголовочном файле? #1
main.cpp
C++
1
2
3
4
#include "aa.h"
int main(int argc, char** argv){
    return 0;
}
aa.h
C++
1
2
3
4
#ifndef e2_H_
#define e2_H_
int a=0;
#endif /* e2_H_ */
aa.cpp
C++
1
#include "aa.h"
Кликните здесь для просмотра всего текста
17:13:00 **** Incremental Build of configuration Debug for project 1 ****
Info: Internal Builder is used for build
g++ -O0 -g3 -Wall -c -fmessage-length=0 -o main.o "..\\main.cpp"
g++ -O0 -g3 -Wall -c -fmessage-length=0 -o aa.o "..\\aa.cpp"
g++ -o 1.exe main.o aa.o 2.o
aa.o:aa.cpp.bss+0x0): multiple definition of `a'
main.o:J:\Programming\C++\rotacja\1\Debug/../main.cpp:11: first defined here
2.o:2.cpp.bss+0x0): multiple definition of `a'
main.o:J:\Programming\C++\rotacja\1\Debug/../main.cpp:11: first defined here
collect2.exe: error: ld returned 1 exit status

Что я делаю неправильно?
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
RussBear
 Аватар для RussBear
1 / 1 / 1
Регистрация: 06.08.2014
Сообщений: 77
11.08.2014, 19:21     Можно ли объявлять объекты в заголовочном файле? #2
Цитата Сообщение от _20_ Посмотреть сообщение
Что я делаю неправильно?
А что Вы хотели сделать? Объявили и инициализировали переменную а .h файле, а зачем, почему?
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
11.08.2014, 19:39     Можно ли объявлять объекты в заголовочном файле? #3
Цитата Сообщение от _20_ Посмотреть сообщение
Что я делаю неправильно?
Переменная int a оказалась одновременно в двух единицах трансляции (в main.cpp и aa.cpp).
Для того, чтобы этого избежать нужно разнести определение и объявление (сейчас у тебя они, так сказать, в одном флаконе):
C++
1
2
3
4
5
6
7
8
9
10
//aa.h
#ifndef e2_H_
#define e2_H_
extern int a;
#endif /* e2_H_ */
 
//aa.cpp
#include "aa.h"
 
int a = 0;
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
11.08.2014, 19:45  [ТС]     Можно ли объявлять объекты в заголовочном файле? #4
Спасибо, так получается, но можно ли как - нибудь без extern обойтись?
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
11.08.2014, 19:57     Можно ли объявлять объекты в заголовочном файле? #5
Цитата Сообщение от _20_ Посмотреть сообщение
Спасибо, так получается, но можно ли как - нибудь без extern обойтись?
А чем extern не устраивает? Это как раз для твоей ситуации решение.
Или конкретизируй задачу, которую решаешь, - можно будет рассмотреть другие варианты.
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
11.08.2014, 20:02  [ТС]     Можно ли объявлять объекты в заголовочном файле? #6
Эта переменная будет использоваться только несколько раз, но в совершенно разных местах. Было бы хорошо, чтобы она была видна только в тех файлах, где она используется.
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
11.08.2014, 20:30     Можно ли объявлять объекты в заголовочном файле? #7
Цитата Сообщение от _20_ Посмотреть сообщение
Эта переменная будет использоваться только несколько раз, но в совершенно разных местах. Было бы хорошо, чтобы она была видна только в тех файлах, где она используется.
Тогда можешь прописать ее объявление (с extern естественно) непосредственно в тех файлах, где нужно. Т.е. h-файл в данном случае вообще не обязателен. Определение же оставь в каком-то одном файле.

Добавлено через 9 минут
Цитата Сообщение от _20_ Посмотреть сообщение
Было бы хорошо, чтобы она была видна только в тех файлах, где она используется.
И все равно не понятно как тебе помешает extern, ведь если ты не подключишь в какой-либо файл aa.h, то переменная a и не будет видна. А если хочется использовать только в строго фиксированных файлах, то тогда см. совет выше. Просто не делаем extern int a; в h-файле, вместо этого пишем его сразу в тех файлах, где надо.
Это должна быть именно модифицируемая из разных мест переменная?
Еще можно предложить сделать две функции интерфейсом к ней:
C++
1
2
3
4
5
6
7
8
9
// h
void set_a(int a);
int  get_a(); 
 
//cpp
static int a = 0;
 
void set_a(int v) {  ::a = v; }
int get_a() { return ::a; }
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
11.08.2014, 20:32  [ТС]     Можно ли объявлять объекты в заголовочном файле? #8
Спасибо. Я слышал где - то, что глобальных переменных лучше избегать, но так и не узнал, почему. Так ли это?
Не лучше ли тогда её в абстрактный класс поместить и отсылаться к этому классу?
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
11.08.2014, 20:39     Можно ли объявлять объекты в заголовочном файле? #9
Цитата Сообщение от _20_ Посмотреть сообщение
Я слышал где - то, что глобальных переменных лучше избегать, но так и не узнал, почему. Так ли это?
Лучше избегать. В основном проблемы связаны с сопровождением кода и пониманием его другими людьми. Хотя есть и чисто технические проблемы, связанные с временем жизни таких переменных в разных файлах (единицах трансляции, если быть точным). Это время относительно двух разных переменных из разных файлов не определено - нельзя сказать какая когда создастся и, соответственно, уничтожится. Относительно друг друга.
Цитата Сообщение от _20_ Посмотреть сообщение
Не лучше ли тогда её в абстрактный класс поместить и отсылаться к этому классу?
Мы как-то ловко перескочили от переменных к абстрактным классам. Без сформулированной задачи здесь ничего не посоветуешь (если только ты не телепат). Ты можешь быть прав, а можешь быть и не прав, в зависимости от того, чего требует задача (именно задача, а не какой-то конкретный код, код - это лишь инструмент), которую ты решаешь.
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
11.08.2014, 20:39  [ТС]     Можно ли объявлять объекты в заголовочном файле? #10
Я только начал графический движок изучать Irrlicht, переменная а - это IrrlichtDevice *device на самом деле, ну и подобные вещи вроде менеджера сцны. Хотел я её с глаз долой убрать, но чтобы всегда под рукой была. Сейчас думаю, может всё - таки лучше делать класс - обёртку с геттерами и сеттерами?
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
11.08.2014, 20:50     Можно ли объявлять объекты в заголовочном файле? #11
_20_,
Класс обычно лучше. Но прежде чем его (их) писать, нужно продумать архитектуру (что с чем взаимодействует, кто выделяет ресурсы, и т.д.). Если этого не сделать, то скорее всего не получится ничего хорошего. Если нет опыта в ООП, сначала лучше книжку почитать толковую по общим моментам. А для тестовых примеров для освоения API движка, как мне кажется, нет смысла городить много кода. Ведь тут главное понять принцип работы. А вот когда соберешься писать свою игру, то тут и пригодятся и знания ООП, и проработка архитектуры.
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
12.08.2014, 02:35  [ТС]     Можно ли объявлять объекты в заголовочном файле? #12
Немного опыта в ООП есть, но хвалиться не буду, т.к. самоучка. Для тестовых примеров можно всё в одном файле написать, здесь я с Вами согласен. Но ведь ничего плохого не случиться, если я попутно чему - нибудь научусь? Тем более, что по теме, которую мы затронули у меня полнейшая каша в голове. Если Вы не будете против, я постараюсь с ней разобраться с Вашей помощью. Скорее всего я буду Вас о чём - то переспрашивать, но прошу Вас не серчать, это не от того, что я невнимательно Ваши посты читаю, а от того, что я в чём - то не уверен и хочу дополнительно убедиться.

Правильно ли я понял, что без использования extern не получиться сделать видимой переменную для 2х разных файлов?
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
12.08.2014, 08:26     Можно ли объявлять объекты в заголовочном файле? #13
Цитата Сообщение от _20_ Посмотреть сообщение
Правильно ли я понял, что без использования extern не получиться сделать видимой переменную для 2х разных файлов?
Непосредственно и одну и ту же - нет. Можно использовать статические переменные классов или функции доступа (я выше показывал), как альтернативу. Зависит от области применения и той задачи, которую мы этим кодом описываем.

Добавлено через 16 минут
Цитата Сообщение от _20_ Посмотреть сообщение
Но ведь ничего плохого не случиться, если я попутно чему - нибудь научусь?
Дык, если прочитать мой пост, то я там как раз и предлагаю попутно учиться. Только сперва по-отдельности (или чередовать). У всех конечно по-разному, но подход "в омут с головой" я считаю не очень перспективным. В смысле если браться за сложную задачу без подготовки (хотя бы минимальной) в надежде попутно во всем разобраться, то скорее всего в конце будет ждать разочарование. Дома все-таки на фундаменте строят, иначе они падают. А те, у кого все-таки хватает упорства из этого омута вылезти, все-таки довольно сильно проигрывают (в основательности знаний) тем товарищам, у которых изначально была какая-то база. Это так, из личных наблюдений.
Я убежден, что программирование имеет мало общего с шаманством, и если ты что-то делаешь, то ты должен быть уверен что это и как это работает. Поэтому я и советовал именно тот вариант, который советовал. Но выбор, как всегда, за тобой, я никого не принуждаю и, естественно, помогу тебе в любой случае, какой бы ты не выбрал
-=ЮрА=-
Заблокирован
Автор FAQ
12.08.2014, 09:12     Можно ли объявлять объекты в заголовочном файле? #14
_20_, твою проблему можно разрулить используя namespace
//обычный namespace
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
 
namespace multiple
{
  //  namespace
  //  {
        int a = 5;
  //  }
      int getVal(){
        return a;
      }
}
 
using namespace multiple;
int main(){
    cout<<a<<endl;
    cout<<getVal()<<endl;
    return 0;
}
http://codepad.org/kXn942Gv

//анонимный namespace
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
 
namespace multiple
{
    namespace
    {
        int a = 5;
    }
      int getVal(){
        return a;
      }
}
 
using namespace multiple;
int main(){
    cout<<a<<endl;
    cout<<getVal()<<endl;
    return 0;
}
http://codepad.org/7BTJzwb0
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
12.08.2014, 13:49  [ТС]     Можно ли объявлять объекты в заголовочном файле? #15
Спасибо. Подводя итог, для реализации доступа к одной и той же переменной можно использовать глобальные переменные extern (нежелательно) и пространства имён namespace. Так же можно сделать переменную статическим полем класса (можно абстрактного), не статическим полем класса, если класс - синглетон, или же приватным полем класса, а доступ раздавать друзьям.

Я ничего не пропустил?
Равносильно ли это использование namespace'ов использованию статических полей класс, или есть тут свои тонкости?
Реализованны ли в С++ внутренние классы?

2 DrOffset
Не могли бы Вы уточнить, что Вы понимаете под сложной задачей и какую именно подготовку Вы считаете минимальной? Без этого, Ваше сообщение видется мне слишком абстрактным для того, чтобы иметь практическое значение.
-=ЮрА=-
Заблокирован
Автор FAQ
12.08.2014, 20:21     Можно ли объявлять объекты в заголовочном файле? #16
Цитата Сообщение от _20_ Посмотреть сообщение
Равносильно ли это использование namespace'ов использованию статических полей класс, или есть тут свои тонкости?
- подумай можешь ли ты изменить переменную а из неймспейса и можешь ли ты её изменить если она статическая
Цитата Сообщение от _20_ Посмотреть сообщение
Реализованны ли в С++ внутренние классы?
- их разве кто-то запрещал?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;
 
namespace multiple
{
    class cOut
    {
         class cInner{
            public :
            int param;
            cInner(){
                param = 3;
            }
        };
        protected:
           int b;
           cInner cs;
        public:
        cOut(){
            b = 5 + cs.param;
        }
        int getParam(){
            return b;
        }
    };
    namespace
    {
        int a = 5;
        cOut pOut;
    }
      int getVal(){
        return a;
      }
}
 
using namespace multiple;
int main(){
    a = a + 10;
    cout<<a<<endl;
    cout<<getVal()<<endl;
    cout<<pOut.getParam()<<endl;
    return 0;
}
http://codepad.org/cwIMB1vF
DrOffset
6457 / 3831 / 885
Регистрация: 30.01.2014
Сообщений: 6,627
12.08.2014, 23:54     Можно ли объявлять объекты в заголовочном файле? #17
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от _20_ Посмотреть сообщение
Спасибо.
Для спасибо тут есть специальные кнопочки. Лично мне это все равно, а вот кому-то может быть важно.
Цитата Сообщение от _20_ Посмотреть сообщение
Подводя итог, для реализации доступа к одной и той же переменной можно использовать глобальные переменные extern (нежелательно) и пространства имён namespace.
Здесь даже уже чуть-чуть более развернуто - уже займет пару страниц объяснений. Ну да ладно, я попробую. Сразу предупреждаю, кое-какие вещи я буду опускать или упрощать, предполагая, что в будущем ты сам в них разберешься. Мое время все-таки ограничено и писать здесь целую книгу в мои планы не входит
Звездочками буду помечать некоторые моменты, которые могут быть дополнены, т.к. я за неимением времени здесь освещаю не все аспекты и тонкости.

Начать нужно с причин твоей первоначальной проблемы, "multiple definition".
Корень проблемы кроется в особенности метода компиляции исходного кода в С и С++. Он раздельный. Т.е. если мы имеем файлы
  • a1.h
  • a1.cpp
  • a2.h
  • a2.cpp
пусть a1.h подключается через препроцессорную директиву #include в a1.cpp
пусть a2.h и a1.h подключается в a2.cpp.
Что произойдет если мы начнем сборку программы из этих файлов? Препроцессор "вставит" содержимое файла a1.h в a2.cpp. Получится некий временный "файл", который является результатом работы препроцессора - этот "файл" называется единицей трансляции. Далее этот "файл" будет компилироваться компилятором С++ в объектный модуль. Тоже самое происходит с файлом a2.cpp, препроцессор "вставляет" содержимое a2.h и a1.h, получившийся объединенный "файл" (единицу трансляции) компилятор транслирует в объектный модуль. Итого у нас два объектных модуля, которые ничего не знаю друг про друга. Они скомпилированы независимо. Связыванием в единый исполняемый модуль занимается компоновщик (или линкер, или линковщик - кому как нравится). Вот именно компоновщик и выдавал твою ошибку "multiple definition", т.к. переменная int a; определенная в одном общем файле, который независимо был скомпилирован в составе разных единиц трансляции в глазах линкера "раздвоилась" (недопущению такого "размножения" посвящено правило одного определения (ODR)). Вот и получилось у нас две переменные, конфликтующие из-за общего имени.

Очень тесно с этим связано понятие linkage(связывание). Оно может быть внутренним(internal), внешним(external) или отсутствовать(no linkage). Внешнее связывание - это когда имя видно линкеру на границе объектного модуля, благодаря этому оно может быть использовано в нескольких модулях как общее. Глобальная переменная по умолчанию имеет внешнее связывание, что и вызвало ошибку в первом посте, т.к. согласно ODR не может быть двух сущностный с одним и тем же именем с внешним связыванием. Внутреннее связывание, это напротив, когда линкер не может использовать имя для связи между разными объектными модулями. Отсутствием связывания характеризуются автоматические переменные, константы времени компиляции, безымянные классы, перечисления и т.п. За подробностями прошу в стандарт С++, параграф 3.5.

Первое решение которое я предложил - это extern.
extern - это один из спецификаторов класса памяти. Их много: static, auto (до С++11), register, extern и thread_local (в С++11). Все они так или иначе влияют на linkage.
Основное* назначение extern в том, что он позволяет указать, что некое имя имеющее (или потенциально способное иметь) внешнее связывание находится где-то (возможно в другом объектном модуле). Где именно выясняет линкер. В данном случае extern int a; выступает в той же роли, что и прототип у функции (кстати свободные функции по умолчанию имеют как раз внешнее связывание, поэтому обычно* нам не надо писать extern перед каждым прототипом - он там подразумевается), создавая лишь объявление. А определение мы предусмотрительно оставили лишь в одной единице трансляции. После этих действий ошибка пропала, т.к. мы объяснили линкеру, что переменная определена в одном месте, а вынесенное в общее место определение с extern является декларацией доступа, ссылкой (linker reference) на нее. Если залезть глубже, то в тех объектных файлах где было определение с extern будет добавлена информация, что переменная не определена и определение может находиться в другом месте. Можно подробнее, например, здесь посмотреть (инфа для unix, но принцип не меняется). Кстати если мы так и не напишем ни одного определения, а оставим лишь объявления с extern, то линкер имя естественно не найдет и ошибка уже будет другая - "undefined reference".

Теперь пара слов о namespace. namespace сам по себе означенную проблему не решает. В С++ есть понятие - глобальное пространство имен. Если не предпринято никаких действий, то глобальные имена определяются в нем. То, что мы сузили область видимости, добавив свой namespace, ничего по сути не изменило и, если рассмотреть твой первый пример с добавлением твоей переменной "int a" в namespace, то ничего не изменится, ошибка будет такая же как и прежде.
Теперь отдельно про анонимный namespace. Действительно, он как бы решает твою проблему, убирая ошибку. Но вот тут самое время вспомнить про linkage. Анонимный namespace определяет для имени internal linkage (внутренее связывание). Это означает, что в каждой из единиц трансляции, куда попадет такая переменная, определенная в анонимном namespace будет содержать свой экземпляр этой переменной, недоступный линкеру для связывания. Т.е. переменных так и будет две как и прежде, просто из-за внутреннего связывания линкер не будет их видеть при компоновке. Естественно изменения с одной переменной в одном объектном модуле никак не затронут другую. Практически тоже самое* можно сказать про переменную, объявленную со спецификатором static (не в классах, а глобально), он тоже определяет внутреннее связывание и эффект в нашем случае будет идентичный.

Все остальные перечисленные в цитате ниже способы
Цитата Сообщение от _20_ Посмотреть сообщение
сделать переменную статическим полем класса (можно абстрактного), не статическим полем класса, если класс - синглетон, или же приватным полем класса, а доступ раздавать друзьям.
так или наче зависят от этих фундаментальных вещей, которые я только что объяснил. Функции доступа, как в одном из моих примеров ранее, синглтон - это дополнительные абстракции над переменными с глобальным (правильно статическим, или static storage durationб Вот кстати неплохая статья про классы хранения) размещением.
Статические же поля классов, например такие:
C++
1
2
3
4
5
6
7
8
9
10
11
 
//h
class A
{
public:
    static int a;
    static int b;
};
//cpp
int A::a = 2;
int A::b = 3;
- это вариация на тему этого:
C++
1
2
3
4
5
6
7
8
9
10
11
12
//h
namespace A
{
    extern int a;
    extern int b;
}
//cpp
namespace A
{
    int a = 2;
    int b = 3;
}
Да, у классов есть контроль доступа, наследование. Поэтому в общем случае одно на другое не заменяется, но важно понимать, что есть что и почему. Если мы оставим определение статических переменных в h-файле и соберем программу по типу твоего изначального примера, то получим все те же ошибки двойного определения.
Цитата Сообщение от _20_ Посмотреть сообщение
Так же можно сделать переменную статическим полем класса (можно абстрактного), не статическим полем класса, если класс - синглетон, или же приватным полем класса, а доступ раздавать друзьям.
Можно, в зависимости от задачи, отдать предпочтение одному из этих способов.

Цитата Сообщение от _20_ Посмотреть сообщение
Не могли бы Вы уточнить, что Вы понимаете под сложной задачей и какую именно подготовку Вы считаете минимальной?
Я писал этот пост целый час и все равно не раскрыл до конца всех тонкостей (на самом деле почти совсем ничего не раскрыл). Минимальная подготовка, это когда мне не нужно будет так подробно расписывать все аспекты, чтобы ты понял о чем я говорю. Иными словами нужно прочитать хотя бы книжку Липпмана или Страуструпа целиком и вынести из них хотя бы процентов 40 знаний. Это нужно для того, чтобы инструмент (язык С++) не превратился из помощника во врага, с которым ты будешь вынужден постоянно бороться, из-за отсутствия понимания почему и как он работает.
Подчеркну, что эти все рассуждения ведутся в контексте потенциального решения тобой сложной задачи, например такой, как написание игры, я же правильно понял, иначе зачем изучать API игрового движка? И это опять же не значит, что нельзя одновременно что-то писать и учиться - можно, но нужно быть готовым к тому, что все это окажется работой в корзину и все придется переделывать. И возможно не один раз. Тестовые же примеры и обучающие минипрограммки нужно писать в любом случае, о них сейчас речи нет.
Движок является относительно сложной программной системой и его понимание в той или иной степени упирается и в знания языка. При проектировании любого API, в том числе и API движка, применяются идиомы или концепции, которые строятся на каких-либо языковых особенностях. Чтобы эффективно использовать API нужно так или иначе представлять как работает инструмент С или С++. Именно с учетом этого был дан совет подтянуть С++, в противовес попытке изучить все скопом "за одно". Хотя я и не отрицал и не отрицаю, что, возможно, у кого-то и так получится.

В общем что-то я увлекся. Надеюсь что-то полезное ты для себя вынесешь.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
16.08.2014, 01:03     Можно ли объявлять объекты в заголовочном файле?
Еще ссылки по теме:

C++ Как подключить библиотеки в заголовочном файле?
Можно ли объявлять объекты в заголовочном файле? C++
C++ Сортировка пузырьком функцией в заголовочном файле

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
_20_
9 / 8 / 1
Регистрация: 29.09.2011
Сообщений: 178
16.08.2014, 01:03  [ТС]     Можно ли объявлять объекты в заголовочном файле? #18
Спасибо Вам большое за столь развёрнутый ответ. Хоть Вы и говорите, что много не раскрыли, определённую ясность в моё понимание С++ Вы внесли. На данный момент у меня не осталось никаких вопросов, чтобы спрашивать.

Плюсы я не сразу не ставил по той причине, что я готов ставить плюсы каждому, кто мне помогает за каждый его пост. То есть, если применительно к Вам, за каждый пост в теме готов поставить Вам плюс, хотя Вам это и не важно. Поэтому я обычно рассавляю плюсы после обсуждения за самые полезные посты.
Yandex
Объявления
16.08.2014, 01:03     Можно ли объявлять объекты в заголовочном файле?
Ответ Создать тему
Опции темы

Текущее время: 23:36. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru