Безымянное пространство имен, шаблонные функции и множественное включение - PullRequest
0 голосов
/ 10 ноября 2019

(> = c ++ 14)

В настоящее время я использую пространство имен World в качестве (своего рода) статического класса. Вот небольшой (и глупый) пример того, что не так:

world.hpp

#pragma once

namespace World {
    namespace {
        template<typename T>
        std::vector<T> components ;
    }

    template<typename T>
    T get(int i) {
        return components<T>[i] ;
    }

    template<typename T>
    void add (T elm) {
        components<T>.push_back(elm) ;
    }
}

layer.hpp

#pragma once

class Layer {
    public:
        float get(int i) ;
        void add(float elm) ;
} ;

layer.cpp

#include "layer.hpp"
#include "world.hpp"

float Layer::get(int i) { return World::get<float>(i) ; }
void Layer::add(float elm) { World::add<float>(elm) ; }

main.cpp

#include "world.hpp"
#include "layer.hpp"
#include <iostream>

int main() {

    Layer layer ;

    for (int i=0 ; i<5 ; ++i) {
        World::add<int>(i) ;
        layer.add(i/5.f) ;
    }

    for (int i=0 ; i<5 ; ++i) {
        std::cout << "get<int>:" << World::get<int>(i) << std::endl ;
        std::cout << "Layer::get:" << layer.get(i) << std::endl ;
        std::cout << "get<float>:" << World::get<float>(i) << std::endl ; // <-- ! SEGFAULT
    }

    return 0 ;
}

Я понимаю, что каждый раз, когда мой мирФайл .hpp включается в .cpp , он создает новую переменную компоненты , поэтому, когда я вызываю layer.get и World:: get он не будет обращаться к одному и тому же вектору (хотя это то, что я хочу). Я предполагаю, что это как-то связано с static или external linkage (или я ошибаюсь?), Но я абсолютно не знаю, как с этим бороться. Чего мне не хватает?

[Редактировать] Я хочу, чтобы вектор компоненты был уникальным, поэтому каждый файл, содержащий "world.hpp", ссылается на один и тот же вектор.

1 Ответ

1 голос
/ 10 ноября 2019

Ваша проблема не имеет ничего общего с включением или связыванием файлов. components - это шаблон переменной, а не фактическая переменная. Для каждого типа вы создаете его (в вашем случае float и int), явно или неявно, компилятор создаст для вас переменную.

Итак, что у вас в основном есть:

  • с World.add<int>(...) в main.cpp вы создаете переменную World::components<int> и заполняете ее значениями.
  • с layer.add(...) в main.cpp вы вызываете World::add<float> в layer.cpp, который создастпеременная World::components<float>

Обе переменные, это то, как работают шаблоны. То же самое происходит с вашими шаблонами функций в World. Для каждого типа создаются шаблоны, компилятор создаст для вас новую функцию.

Редактировать после комментариев :

Возможно, я все еще что-то не так понял, но

  • layer.get(i) и
  • world::get<float>(i)

дают мне то же значение, добавленное с layer.add(i/5.f), так что оно работает, как должно, нене так ли?

Оба дают значения в World::components<float>, тогда как World::get<int>(i) дает мне значения в World::components<int>

Вывод для программы (после исправления некоторых небольших ошибок):

get<int>:0
Layer::get:0
get<float>:0
get<int>:1
Layer::get:0.2
get<float>:0.2
get<int>:2
Layer::get:0.4
get<float>:0.4
get<int>:3
Layer::get:0.6
get<float>:0.6
get<int>:4
Layer::get:0.8
get<float>:0.8

Второе редактирование (копание) :

Так как что-то должно или не должно работать (и не только иногда) я начал копать. Действительно, компилятор создает два экземпляра World::components<float>. После

g++ -o prog layer.cpp main.cpp

и

nm prog | grep components

я получаю

0000000000407210 b _ZN5World12_GLOBAL__N_110componentsIfEE
0000000000407230 b _ZN5World12_GLOBAL__N_110componentsIfEE
00000000004071f0 b _ZN5World12_GLOBAL__N_110componentsIiEE

, что говорит мне, что два components<float> и один component<int> находятся в разделе BSSобъектный файл.

В зависимости от порядка компиляции, программа работает либо как ожидалось, или выдает ошибку сегментации. Вышеуказанное состояние заказа работает,

g++ -o prog main.cpp layer.cpp

приводит к ошибке сегментации, о которой сообщает op. Не проверяя доступ ко всем трем компонентам в скомпилированном файле, я предполагаю, что компиляция main.cpp создает версию вектора float и int, компиляция layer.cpp создает вторую версию floatвектор. Первые два инициализируются в main.cpp, а третий остается пустым, что приводит к неопределенному поведению при доступе к нему с помощью оператора [] позже. Кажется, если компилировать наоборот, компоновщик разрешает каждый доступ к components<float> к одному и тому же экземпляру в разделе BSS, так что программа на самом деле работает нормально, но, вероятно, выдает неожиданное / неопределенное поведение при других обстоятельствах (например, есливторой экземпляр components<float> доступен как-то еще.

Итак, необходимо решение. Я изменил world.hpp на

#pragma once

#include <vector>

namespace World {
    template<typename T>
    struct Comp {
        static std::vector<T> components ;
    };  

    template<typename T>
    T get(int i) {
        return Comp<T>::components[i] ;
    }   

    template<typename T>
    void add (T elm) {
        Comp<T>::components.push_back(elm) ;
    }   
}

template<typename T>
std::vector<T> World::Comp<T>::components;

, который работает независимо от порядкакомпиляции / связывания.

Третье редактирование (примечания): Кстати. Я прочитал в ваших комментариях ниже вашего вопроса, что вы хотите скрыть components извне или ограничить доступ к нему. Если это ваша цель, вы также можете сделать следующее:

  • Изменить World из пространства имен на класс
  • Объявить components как private и static член
  • Объявите две ваши функции как public и static

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...