Инициализация членов типа статической структуры C ++

Возможный дубликат: ссылка

Всем привет,

Есть странная вещь, которую я не понимаю в статических переменных-членах. Если «определение» (я не уверен, что это правильное слово) для статических переменных находится в файле заголовка класса, компилятор выдает ошибки связывания, однако, если они находятся в файле cpp, все в порядке.

У меня есть такой класс (не вставлял все):

UserInterface.h

class UserInterface
{
public:
    UserInterface(void);
    ~UserInterface(void);

    // Some method declarations here
private:
    // Some more methods declarations here
    // VARIABLES
    static bool                               m_undoRequested;
    static ChessViewConstants::MENU_STATE     m_displayState;
    static ChessModelConstants::PieceMovement m_pieceMovement;
};
// THESE DO NOT WORK (linking errors)
//bool UserInterface::m_undoRequested = false;
//ChessViewConstants::MENU_STATE UserInterface::m_displayState = ChessViewConstants::MAIN_MENU;
//ChessModelConstants::PieceMovement UserInterface::m_pieceMovement(1, 1, 1, 1);

UserInterface.cpp

#include "UserInterface.h"

// These do work.
bool UserInterface::m_undoRequested = false;
ChessViewConstants::MENU_STATE UserInterface::m_displayState = ChessViewConstants::MAIN_MENU;
ChessModelConstants::PieceMovement UserInterface::m_pieceMovement(1, 1, 1, 1);

// Implementation....

ChessConstants.h

namespace ChessModelConstats{
    // Some stuff here...

    struct PieceMovement {

    // A simple Constructor
    PieceMovement(int originRow = -1, int originCol = -1, 
                  int targetRow = -1, int targetCol = -1)
    : m_originRow(originRow), m_originCol(originCol),
      m_targetRow(targetRow), m_targetCol(targetCol) 
    {
    }

        // Members
        int m_originRow;
        int m_originCol;
        int m_targetRow;
        int m_targetCol;
    };

// More stuff here....
}

Итак, почему статические переменные должны быть реализованы внутри файла cpp? Почему я не могу добавить в конец файла заголовка?

Второй вопрос: как я могу инициализировать структурную переменную (m_pieceMovement) следующим образом:

m_pieceMovement.m_originCol = -1;
m_pieceMovement.m_originRow = -1;
m_pieceMovement.m_targetCol = -1;
m_pieceMovement.m_targetRow = -1;

Кажется, мне здесь не хватает фундаментальной информации, не стесняйтесь давать советы новичкам тут и там :)

Заранее спасибо,

Джон Джон

РЕДАКТИРОВАТЬ: Вот ошибки связывания:

1> MasterController.obj: ошибка LNK2005: «private: static bool UserInterface :: m_undoRequested» (? M_undoRequested @ UserInterface @@ 0_NA) уже определен в Execution.obj 1> MasterController.obj: error LNK2005: «private: static enum ChessViewConstants: : MENU_STATE UserInterface :: m_displayState "(? M_displayState @ UserInterface @@ 0W4MENU_STATE @ ChessViewConstants @@ A), уже определенный в Execution.obj 1> MasterController.obj: error LNK2005: "ove private: static struct ChessInModelConstants :: PieceModelConstants :: PieceModelConstants :: Piece (? m_pieceMovement @ UserInterface @@ 0UPieceMovement @ ChessModelConstants @@ A) уже определено в Execution.obj 1> UserInterface.obj: error LNK2005: «private: static bool UserInterface :: m_undoRequested» (? m_undoRequested @ UserInterface @@ уже определено 0_undoRequested @ UserInterface @@ в Execution.obj 1> UserInterface.obj: ошибка LNK2005: "private: static enum ChessViewConstants :: MENU_STATE UserInterface :: m_displayState"? m_displayState @ UserInterface @@ 0W4MENU_STATE @ ChessViewConstants @@ A) уже определено в Execution.obj 1> UserInterface.obj: ошибка LNK2005: «private: static struct ChessModelConstants :: PieceMovement UserInterface :: m_pieceMovement» (? m_pieceMovement @ UserInterface @@ 0UPieceMovement @ ChessModelConstants @@ A. уже определено в 1 >cution D: \ C ++ \ CheatersChess \ Debug \ CheatersChess.exe: фатальная ошибка LNK1169: обнаружен один или несколько многократно определенных символов


person John John    schedule 11.01.2014    source источник
comment
Ваш второй вопрос совершенно непонятен. В структуре есть конструктор по умолчанию, кроме того, эти элементы данных являются общедоступными, так в чем проблема с их инициализацией ?!   -  person Vlad from Moscow    schedule 11.01.2014
comment
Если у вас есть два вопроса, возможно, вам стоит задать два отдельных вопроса.   -  person Alan Stokes    schedule 11.01.2014
comment
Ну, бывают случаи, когда я хочу объявить член статического типа Object. Я бы хотел инициализировать его с помощью object- ›initialize () или чего-то в этом роде. Конечно, я хотел бы инициализировать его только один раз. Тогда как мне это сделать? Я не могу сделать это в конструкторе, кажется, единственный вариант - создать нечто вроде того, чего я бы хотел избежать.   -  person John John    schedule 11.01.2014
comment
Вы не можете вызвать функцию типа object->initialize() вне тела функции. Следовательно, для определения статического объекта нельзя использовать функцию-член, например initialize(); вы должны сделать это в конструкторе (но конструктор, конечно, может вызывать initialize()). Если по какой-то причине это действительно абсолютно невозможно для вас, то в крайнем случае вы можете определить отдельный глобальный объект другого типа и убедиться, что конструктор этого объекта вызывает функцию initialize() первого объекта.   -  person jogojapan    schedule 11.01.2014


Ответы (1)


Стандарт C ++ включает правило, называемое правилом одного определения. Часть его 3.2 / 3:

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или переменной, которая используется odr в этой программе; [...]

Когда статический член вашего вопроса определен в файле заголовка, его определение будет включено в каждый файл .obj, который скомпилирован из файла .cpp, который включает этот заголовок. Поскольку многие файлы могут включать этот заголовок, вы получаете несколько определений, что является нарушением этого правила. (Обратите внимание, что для этого нарушения не имеет значения, идентичны ли все эти определения.)

Принимая во внимание, что когда вы помещаете определение в файл .cpp, определение включается только в файл .obj, который скомпилирован из этого одного файла .cpp, что не вызывает дублирования определений при связывании программы.

Что касается второго вопроса: вам нужно определить конструктор, который принимает желаемые значения для членов в качестве аргументов. Фактически, вы уже это сделали. Вы можете использовать это для определения статического члена (в файле .cpp):

ChessModelConstants::PieceMovement UserInterface::m_pieceMovement(-1,-1,-1,-1);
person jogojapan    schedule 11.01.2014
comment
Ну, защита #ifndef предназначена для множественных включений. И есть только один класс, который включает класс UserInterface. Думаю, мне нужна дополнительная информация о процессе связывания (я не жду этого от вас). Спасибо за разъяснения. - person John John; 11.01.2014
comment
Защита включения не помогает, когда несколько файлов .cpp включают один и тот же заголовок, и каждый из этих файлов .cpp компилируется отдельно. Include-guards помогает только в рамках одного процесса компиляции, а не для нескольких отдельных процессов компиляции. То есть, include-guard действует только в пределах одной единицы трансляции. И из ваших сообщений об ошибках кажется, что по крайней мере MasterController.cpp и Execution.cpp включают UserInterface.h и компилируются отдельно. - person jogojapan; 11.01.2014
comment
Ну, MasterController.h включает UserInterface.h, а Execution.cpp включает MasterController.h, так что .... Я думаю, вы правы. Поскольку никто не будет включать файлы cpp, я думаю, можно с уверенностью предположить, что статические переменные объявлены в файлах cpp. Но как мне это сделать с файлами только заголовков (только определения классов и некоторые простые реализации методов)? Есть ли способ сделать это с классом только для заголовка? - person John John; 11.01.2014
comment
Во-первых, да, действительно то, что я сказал выше, верно только тогда, когда вы не запускаете #include-ing .cpp файлы. Во-вторых, с классом только для заголовков у вас не может быть статических членов. Единственным исключением являются статические члены интегрального или перечисляемого типа, когда они не используются odr, то есть не используются в смысле ODR (правило одного определения). Точное определение этого (которое я не включил выше) немного сложно, но в любом случае вы вряд ли найдете какой-либо практический вариант использования, в котором переменная, которая никогда не используется odr, имеет какой-либо смысл. Но вы можете сделать еще одно: - person jogojapan; 11.01.2014
comment
(продолжение) Вместо статического члена вы можете определить статический член function, который определяет статическую переменную в своем теле и возвращает следующее: static int &myvalue() { static int val; return val; } Затем вы можете использовать myvalue(), как если бы это была статическая переменная . В этом нет реальных недостатков, за исключением того, что вам нужно добавить круглые скобки там, где вы это используете. Это работает в библиотеке только для заголовков, а также работает с нецелыми типами. - person jogojapan; 11.01.2014
comment
Для полноты картины я должен добавить, что переменная static внутри тела функции может вызвать незначительное снижение производительности, но оно будет настолько незначительным, что определенно не будет иметь значения, если вы не будете вызывать эту функцию слишком часто. - person jogojapan; 11.01.2014
comment
Вау, это никогда не приходило мне в голову. С точки зрения реализации каждый раз, когда мне нужен доступ к этой статической переменной, это будет стоить мне больше с точки зрения производительности, поскольку мне придется создавать и удалять стек вызовов, что плохо, но хорошо, что мы можем хранить статическую переменную. Сюда. Отличная идея, спасибо, что поделились. - person John John; 11.01.2014
comment
Стек вызовов не является проблемой, потому что эта очень короткая функция может быть определена внутри определения класса, что делает ее встроенной функцией. Компилятор определенно встраивает вызовы этой функции, чтобы не было реального стека вызовов. Это было бы похоже на использование переменной напрямую. (Возможное незначительное снижение производительности происходит из-за того, что с любой статической переменной внутри функции компилятор должен гарантировать, что переменная инициализируется только во время самого первого вызова функции. Это означает, что он может использовать какой-то if-оператор для проверки если переменная была инициализирована ранее.) - person jogojapan; 11.01.2014
comment
Еще одна вещь: выше я утверждал, что вы почти никогда не будете использовать статический член в сценарии только для заголовка, потому что у вас почти никогда не будет переменной, которую вы никогда не используете odr. На самом деле есть одно исключение: если у вас есть переменная const (целочисленного или перечисляемого типа), и вы никогда не привязываете к ней ссылку и никогда не берете ее адрес, то вы, скорее всего, никогда не будете использовать odr. Так что const int (или const short и т. Д.) Или статический const член какого-либо enum типа может иметь смысл. И вы можете определить это в заголовке (прямо в том месте, где вы это объявляете, а не в отдельной строке). - person jogojapan; 11.01.2014