【问题标题】:Unnamed namespace, templated functions and multiple inclusion未命名的命名空间、模板函数和多重包含
【发布时间】:2019-11-10 13:26:05
【问题描述】:

(>= 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) ; }

ma​​in.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 ;
}

我知道每次我的 world.hpp 文件被包含在 .cpp 中时,它都会创建一个新变量 components 所以当我调用 layer.getWorld::get 它不会访问相同的向量(尽管这是我想要的)。我想这与 staticexternal 链接(或者我错了吗?)有关,但我完全不知道如何处理它。我错过了什么?

[编辑] 我想要的是向量 components 是唯一的,所以每个包含“world.hpp”的文件都指的是完全相同的向量。

【问题讨论】:

  • 每次包含world.hpp 时,它都会创建一个嵌套在namespace World 内的 未命名命名空间,这可能不是您想要的。
  • 你能解释一下你的意思是“如何处理它”。如果您不希望每个翻译单元在此处使用匿名命名空间,请不要使用匿名命名空间。
  • @RichardCritten 确实,但由于我有模板函数,我无法将未命名的命名空间移动到 .cpp 文件。 SamVarshavchik 我试着让我的目标更清晰一些。
  • 为什么会有匿名命名空间?
  • @RichardCritten 从外部隐藏变量,但由于它会引起很多问题,我想如果没有其他解决方案,我会删除它

标签: c++ templates namespaces


【解决方案1】:

您的问题与文件包含或链接无关。 components 是变量模板,不是实际变量。对于您实例化它的每种类型(在您的情况下为 floatint),编译器会显式或隐式地为您创建一个变量。

所以你基本上有什么:

  • 在 main.cpp 中使用 World.add&lt;int&gt;(...) 创建变量 World::components&lt;int&gt; 并用值填充它。
  • 在 main.cpp 中使用 layer.add(...) 在 layer.cpp 中调用 World::add&lt;float&gt;,这将创建变量 World::components&lt;float&gt;

两者都是不同的变量,这就是模板的工作方式。 World 中的函数模板也是如此。对于模板实例化的每种类型,编译器都会为您创建一个新函数。

在 cmets 之后编辑

也许我还是有问题,但是

  • layer.get(i)
  • world::get&lt;float&gt;(i)

给我相同的值,加上layer.add(i/5.f),所以它应该像它应该的那样工作,不是吗?

两者都给出了World::components&lt;float&gt; 中的值,而World::get&lt;int&gt;(i) 给出了World::components&lt;int&gt; 中的值

程序的输出(修复一些小错误后):

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&lt;float&gt; 实例。之后

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&lt;float&gt; 和一个component&lt;int&gt; 驻留在目标文件的BSS 部分。

根据编译顺序,程序要么按预期工作,要么产生分段错误。上面的订单状态有效,

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

导致操作报告的分段错误。在不检查对编译文件中所有三个组件的访问的情况下,我假设 main.cpp 的编译创建了向量的 floatint 版本,layer.cpp 的编译创建了第二个 float 版本向量。前两个在 main.cpp 中初始化,而第三个保持为空 - 稍后使用 [] 运算符访问它时会导致未定义的行为。似乎如果以相反的方式编译,链接器将对 components&lt;float&gt; 的每次访问解析为 BSS 部分中的同一实例,因此程序实际上运行良好,但可能会在不同情况下产生意外/未定义的行为(例如,如果components&lt;float&gt; 的第二个实例以某种方式在其他地方被访问。

因此,需要一个解决方案。我已将 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;

无论编译/链接的顺序如何,哪个都有效。

第三次修改(备注): 顺便提一句。我在您的问题下方的 cmets 中读到您想从外部隐藏 components 或限制对其的访问。如果这是您的目标,您还可以执行以下操作:

  • World 从命名空间更改为类
  • components 声明为privatestatic 成员
  • 将你的两个函数声明为publicstatic

在这种情况下,您还必须按照我在上面示例中所做的方式(最后两行)定义您的私有静态成员。

【讨论】:

  • 是的,我得到了这部分。但是 World::add 创建的 World::::compenents 与 Layer::add(float) 创建的不一样,这不是我想要的行为。跨度>
  • 啊,好吧,对不起,我应该在说明显而易见的情况之前编译您的代码并对其进行测试。我会检查的。
  • 好吧,这看起来不错!但它仍然无法在我的计算机上运行,​​除非我删除未命名的命名空间.. 你能告诉我你做了什么更改以及你正在使用的编译器吗?
  • 我使用 gcc-Version 9.2.1 20190827 (Red Hat 9.2.1-1) (GCC),没有提供额外的标志。我在 main.cpp(错别字)中将 world 更改为 World,并在 layer.hpp 中的类定义之后添加了缺少的分号。我目前正在检查已编译的目标文件以深入了解。
  • 我又试了一次,现在它可以工作了,虽然我不知道发生了什么变化……非常抱歉浪费了您的时间。如果我发现以前和现在之间发生了什么变化,我会告诉你的。感谢您花费的时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-14
  • 1970-01-01
相关资源
最近更新 更多