Предположим, у меня есть библиотека только для заголовков. Я упростил это примерно так.

Библиотека только для заголовков Foo.hpp

#ifndef FOO_HPP
#define FOO_HPP

struct Foo{
    static const int A;
};

const int Foo::A = 100;

void SomeMethod(){
   // do some stuff
}
#endif

Затем у меня есть родительский класс (Parent.hpp и Parent.cpp):

#ifndef PARENT_HPP
#define PARENT_HPP

#include "Foo.hpp"

struct Parent {
        virtual void my_method();
};
#endif

Дочерний класс (Child.hpp и Child.cpp)

#ifndef CHILD_HPP
#define CHILD_HPP

#include "Parent.hpp"
#include "Foo.hpp"

struct Child : Parent{
        void my_method();
};
#endif  

В my_method() я просто печатаю переменную Foo::A.

В реальной кодовой базе у меня есть шаблон только в библиотеке заголовка.

Когда я компилирую это, я получил ошибку "множественное определение". Как это исправить?

-2
Bharata 23 Янв 2021 в 14:02

2 ответа

Лучший ответ

Вы выбрали «не встроенный» режим инициализации своего статического члена. Это совершенно законно, но - в этом случае у вас должна быть ровно одна единица перевода (например, скомпилированный файл .cpp), определяющая вашего члена. В противном случае компоновщик C ++ видит одно определение Foo::A в parent.o и второе определение в child.o, и они конфликтуют.

Таким образом,

Решение 1. Переместите определение в другой файл

  1. Создайте Foo.cpp, который включает Foo.hpp и определяет Foo::A.
  2. Удалите определение из заголовка, чтобы оно не повторялось в нескольких местах.

Решение 2. Сделайте определение условным

На самом деле это не рекомендуется, но это работает.

  1. Окружите определение так:

    #ifdef FOO_A_DEFINITION
    const int Foo::A = 100;
    #endif
    
  2. Создайте Foo.cpp, который определяет #FOO_A_DEFINITION, а затем включает Foo.hpp

Это имеет недостаток в использовании макросов, но дает преимущество пользователям-людям, которые видят определение из заголовка.

Решение 3. Инициализировать в теле

Итак, вы спрашиваете себя: «Почему они должны конфликтовать? Я хочу, чтобы компилятор знал, что это одна и та же переменная!»

Один из способов сделать это - инициализировать статический член в теле класса. Поскольку это простой тип, это возможно даже в более старых версиях языкового стандарта. Это также решение, предлагаемое @ idmean:

class Foo {
    public:
        static const int A = 100;
};

Здесь есть предостережение: вы "не совсем" определяете статический член таким образом. Вы можете использовать его, но не во всех случаях, когда вы использовали бы переменную, определенную с помощью решений (1.) или (2.). См. Обсуждение здесь:

См. Обсуждение этого момента здесь:

Определение целочисленных членов static const в определении класса

Решение 4: встроите свой статический член

Это решение - еще один способ сказать компилятору «это всего лишь одно определение для всех», которое работает только в C ++ 17 или новее:

#include <iostream>
class Foo {
    public:
        inline static const int A = 100;
};

Это очень похоже на Решение 3, но на самом деле делает совсем другое!

С помощью этого решения вы фактически определяете статический член несколько раз - в каждой единице перевода - но сообщаете компилятору, чтобы он сохранял только одно определение при связывании и обнаружении многих из них.

Решения (1.) и (2.) ведут себя иначе, чем это решение (4.), когда вы используете динамически подключаемые библиотеки. Видеть:

Где инициализировать статический константный член в с ++ 17 или новее?

Для объяснения.

0
einpoklum 23 Янв 2021 в 11:43

Заменить

class Foo {
    
    public:
        static const int A;
    
};

const int Foo::A = 100;

С участием

class Foo {
    
    public:
        static const int A = 100;
    
};
0
einpoklum 23 Янв 2021 в 11:43