C++ include guard изглежда не работи?

Използвал съм предпазители за включване много пъти преди, но никога не съм разбирал как и защо работят.

Защо следното не работи?

#ifndef CAMERA_CLASS_HPP
#define CAMERA_CLASS_HPP


class camera_class
{
....
};

camera_class glcam = camera_class();


#endif // CAMERA_CLASS_HPP

Грешката е следната: (Вероятно можете да познаете какво ще бъде от заглавието на този въпрос!)

-------------- Build: Debug in System ---------------

Linking console executable: bin/Debug/System
/usr/bin/ld: error: obj/Debug/main.o: multiple definition of 'glcam'
/usr/bin/ld: obj/Debug/camera_class.o: previous definition here
/usr/bin/ld: error: obj/Debug/main.glfunc.o: multiple definition of 'glcam'
/usr/bin/ld: obj/Debug/camera_class.o: previous definition here
collect2: ld returned 1 exit status
Process terminated with status 1 (0 minutes, 0 seconds)
0 errors, 0 warnings

Също така, може ли някой да ми обясни защо предпазителят на главата работи?


person FreelanceConsultant    schedule 07.09.2012    source източник
comment
Вашето разбиране за това какво правят охранителите е погрешно. Те предотвратяват включването на заглавен файл два пъти от един и същ изходен файл. Те не и не могат да предотвратят включването на заглавка два пъти от различни изходни файлове.   -  person john    schedule 07.09.2012
comment
Благодаря Джон, не разбрах тази фундаментална разлика!   -  person FreelanceConsultant    schedule 07.09.2012


Отговори (6)


Изглежда, че искате да създадете единичен glcam обект, който може да се използва на множество места. Бих направил това, като изложа безплатна функция за връщане на static екземпляр. Това е подобно на използването на extern, но намирам, че е малко по-ясно в намерението си.

#ifndef CAMERA_CLASS_HPP
#define CAMERA_CLASS_HPP


class camera_class
{
....
};

camera_class& get_camera();


#endif // CAMERA_CLASS_HPP

// in the CPP

camera_class& get_camera()
{
   static camera_class the_camera;
   return the_camera;
}

Това ви дава възможност да използвате един camera_class екземпляр, без да разчитате на extern, но в същото време не ви принуждава да го използвате като сингълтон, тъй като други области на кода също са свободни да създават свои собствени частни екземпляри.

Това може да се реализира така, както е (безплатна функция) или като static членска функция на camera_class. Избрах първото въз основа на някои отлични съвети от Скот Майерс:

Ако пишете функция, която може да бъде имплементирана или като член, или като не-приятел, не-член, трябва да предпочетете да я имплементирате като функция, която не е член.

Източник: http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197

person Chad    schedule 07.09.2012
comment
... Въпреки че друг въпрос. Защо camera_class& get_camera(); не е метод в класа? Защо е извън дефиницията на класа? - person FreelanceConsultant; 07.09.2012
comment
Страхотен въпрос и заслужава отговор на по-видно място, отколкото в коментар. Вижте последния параграф от моя отговор. Тази връзка съдържа отлична информация, която е конкретно по темата за първоначалния ви въпрос. - person Chad; 07.09.2012

Предпазителят на заглавката ще предотврати множество включвания в една единица за превод. Заглавката може (и е) да бъде включена в множество единици за превод:

// a.cpp:
#include "camera.hpp"

// b.cpp
#include "camera.hpp"

Това ще създаде a.obj и b.obj, всеки от които съдържа дефиниция за glcam. Когато се свържат заедно, за да произведат крайния двоичен файл, получавате грешката с множество дефиниции.

Трябва да декларирате glcam в заглавката и да го дефинирате точно веднъж във файл .cpp:

// camera.hpp
...

extern camera_class glcam;

// camera.cpp
#include "camera.hpp"

camera_class glcam;
person hmjd    schedule 07.09.2012
comment
Това е решението, което избрах. Не ми харесва да имам обект на camera_class в напълно различен файл (main.opengl.cpp), но ще трябва да го направя! - person FreelanceConsultant; 07.09.2012

Основна причина:
Защитата на заглавката предотвратява включването на една и съща заглавка многократно в едно и също преводна единица, но не и в различни преводни единици. Когато включите един и същ заглавен файл в множество единици за превод,
копие на glcam наистина се създава във всяка единица за превод, където включите заглавката.
Стандартът на C++ изисква всеки символ да бъде дефинирано само веднъж (Правило за една дефиниция) и следователно линкерът ви издава грешката.

Решение:
Не създавайте glcam в заглавния файл. Вместо това трябва да се създаде по такъв начин, че да се дефинира само веднъж. Правилният начин да направите това е чрез използвайки ключовата дума extern.

person Alok Save    schedule 07.09.2012
comment
Къде трябва да създам glcam? Трябва ли да го създам в .cpp файла? - person FreelanceConsultant; 07.09.2012
comment
@EdwardBird: Актуализиран, за да отговори на вашето запитване. - person Alok Save; 07.09.2012
comment
extern работи, но винаги съм намирал това за малко объркващо. Предпочитам да използвам метода, предоставен в моя отговор, който постига основно същия резултат, но намирам за по-ясен. Очевидно обаче това е само въпрос на вкус. - person Chad; 07.09.2012
comment
Какво прави extern? Дали това е само помощ, за да работи? - Прочетох статията, но не съм сигурен, че я разбирам. - person FreelanceConsultant; 07.09.2012
comment
А, разбирам, това е като „превключвател“ за свързване, предполагам, че бихте могли да кажете? Което просто го кара да се държи различно. Мисля да погледна в Уикипедия как работят C++ компилаторите и по кое време какви неща се правят. Това трябва да ми помогне да го разбера по-добре. :) - person FreelanceConsultant; 07.09.2012
comment
@EdwardBird: Да. Променен предишен коментар за обяснение. Ако създадете обект в обхвата на файла, тогава по подразбиране той ще има външна връзка. Което означава, че имате достъп до обекта във всички други единици за превод. extern казва на компилатора и линкера, че обектът е дефиниран в някои други TU и ви позволява да създадете обекта в един файл, но да имате достъп до него в други файлове. - person Alok Save; 07.09.2012

Тъй като включвате този файл от няколко файла, вие нарушавате Правилото за една дефиниция:

В цялата програма един обект или невградена функция не може да има повече от една дефиниция

Трябва да поставите дефиницията glcam в изходен файл, а не в заглавен файл, или вместо това да я декларирате като extern и да предоставите дефиниция в някакъв изходен файл.

person mfontanini    schedule 07.09.2012

Защитата за включване предотвратява появата на множество екземпляри на текста във вашия хедър в една компилационна единица (т.е. един .cpp, който изграждате, който се вгражда в .o)

Това не пречи на множество копия на този текст да се появяват в множество компилационни единици.

Така че по време на връзката всяка компилационна единица, която включва този хедър, има a

 camera_class glcam = camera_class();

Като символ. C++ не може да реши, когато се позовава на "glcam", коя единична глобална дефиниция имате предвид. Този от main.o или този от camera_class.o?

person Doug T.    schedule 07.09.2012

Работи добре, получавате само една дефиниция във всеки от вашите изходни файлове.

Проблемът е, че имате множество изходни файлове и линкерът намира множество дефиниции.

В заглавния файл трябва да поставите:

extern camera_class glcam;

И след това в един и само един изходен файл поставете това, което сте имали в заглавката:

camera_class glcam = camera_class();

На този етап ще трябва да сте наясно с проблемите с реда за инициализация. Не се опитвайте да използвате glcam от статични обекти.

person Mark Ransom    schedule 07.09.2012