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)

Это не предотвращает появление нескольких экземпляров этого текста в нескольких единицах компиляции.

Таким образом, во время компоновки каждая единица компиляции, которая включает этот заголовок, имеет

 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