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

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

Восстановить пароль Регистрация
Другие темы раздела
C++ GetFrameTime() выдает нормальное время кадра только со второго прохода по циклу http://www.cyberforum.ru/cpp-beginners/thread1241169.html
while(1) { frameTime = GetFrameTime(); // есть функция возвращает время 1 кадра. func1(frametime); // в эту функцию отправляется время 1 кадра чтобы регулировать скорости от фпс. } Беда в том что GetFrameTime() выдает нормальное время кадра только со второго прохода по циклу. Так как dt не имеет frameTimeOld: dt = currentTime.QuadPart-frameTimeOld; frameTimeOld = currentTime.QuadPart;
C++ Создать независимую функцию, которая будет умножать две матрицы Добрый день всем!:) Такой вопрос. Создала свой класс "двухмерной" матрицы. Хочу создать независимую функцию, которая будет умножать две приходящие матрицы. С указателем на указатели проблем не возникло. Но решила для быстроты работы сделать через одномерные массивы(все мы знаем, что двумерный массив - обман компилятора:-для умножения одномерных массивов путём правил умножения двухмерных матриц.... http://www.cyberforum.ru/cpp-beginners/thread1241166.html
Перегрузка операторов: Можно ли вынести "тело" оператора (расчеты) из H в CPP? C++
Помогите новичку! Пишу класс и вынес его в отдельные файлы *.CPP и *.H Теперь пытаюсь переопределить операторы. Начал с = По примеру отсюда http://habrahabr.ru/post/132014/ class Integer { private: int value; public: Integer& operator=(const Integer& right) { if (this == &right) { return *this; }
C++ Запуск программы с параметрами с командной строки
Добрый день. Хочу решить простую задачу. Пользователь должен запустить программу с параметрами с консоли. Параметров может быть 2 или 3. Либо две символьные строки (массива char), либо две строки и целочисленная переменная. Написал такой код: #include "stdafx.h" #include <iostream> #include "header.h" using namespace std; int _tmain(int argc, char argv) { char path1 = { 0 }, path2 = {...
C++ Временные файлы папки Temp - как избежать ее удаления http://www.cyberforum.ru/cpp-beginners/thread1241137.html
Подскажите пожалуйста! Есть программа, после ее обновления папка sql удаляется из временной папки TEMP... Что можно сделать, чтобы эта папка не удалялась вместе с файлами после обновления?
C++ Консоль не запускается из-за отсутствия DLL Вот что выдал мне компьютер после попытки запустить мою консоль. Как это исправить? Почему консоль не запускается? Может быть, дело в том, что на этом компьютере не установлено Visual Studio, в которой я сделал эту консоль? подробнее

Показать сообщение отдельно
DrOffset
6424 / 3798 / 879
Регистрация: 30.01.2014
Сообщений: 6,591
12.08.2014, 23:54     Можно ли объявлять объекты в заголовочном файле?
Цитата Сообщение от _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 нужно так или иначе представлять как работает инструмент С или С++. Именно с учетом этого был дан совет подтянуть С++, в противовес попытке изучить все скопом "за одно". Хотя я и не отрицал и не отрицаю, что, возможно, у кого-то и так получится.

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