命名空间与静态类

时间:2011-10-18 03:49:14

标签: c++ visual-studio-2010 namespaces

对于我正在进行的项目,我有一堆“库类”。这些本质上是值的相关函数的集合。其中一些库需要在运行时“初始化”。到目前为止,我一直在使用以下设计作为解决方案:

// Filename: Foo.h
namespace my_project
{
namespace library
{
class Foo
{
public:
    static int some_value; // members used externally and internally

    Foo()
    {
        // Lots of stuff goes on in here
        // Therefore it's not a simply member initialization
        // But for this example, this should suffice
        some_value = 10;
        Foo::bar();
    }

    static void bar() { ++some_value; } // some library function

    // no destructor needed because we didn't allocate anything

private:
    // restrict copy/assignment
    Foo(const Foo&);
    void operator=(const Foo&);
};
int Foo::some_value = 0; // since some_value is static, we need this
} // library namespace
static library::Foo Foo;
} // my_project namespace

使用Foo与此类似,例如:

#include "Foo.h"
using namespace my_project;
int main()
{
    int i = Foo.some_value;
    Foo.bar();
    int j = Foo.some_value;
    return 0;
}

当然,这个例子非常简单,但它得到了重点。这种方法对我有四个好处:

  1. 库的用户无需担心初始化。他们不需要在Foo::init();内调用类似main()的内容,因为library::Foomy_project::Foo构建时已初始化。这是这里的主要设计约束。 用户负责初始化库。

  2. 我可以在库中创建各种私有函数来控制它的使用。

  3. 无论出于何种原因,用户都可以选择创建此库的其他实例。但是不允许复制。默认情况下会为它们提供一个实例。这是一项要求。

  4. 我可以使用.语法代替::。但这是个人风格的事情。

  5. 现在,问题是,这个解决方案有什么缺点吗?我觉得我正在做一些C ++不应该做的事情,因为Visual Studio的IntelliSense一直在吓唬我,并且认为my_project::Foo没有声明。可能是因为对象和类都被称为Foo,即使它们位于不同的名称空间中?

    解决方案编译得很好。我只是担心,一旦我的项目规模扩大,我可能会开始有名称含糊不清。此外,我是否通过创建此库的对象来浪费额外的内存?

    我应该坚持使用singleton design pattern作为替代解决方案吗?有没有其他解决方案?

    更新

    在审核了所提供的解决方案,并围绕谷歌寻求各种解决方案之后,我偶然发现了extern。我不得不说我对这个关键字真正做的事情有点模糊;自从我学习C ++以来,我一直很模糊。但在调整我的代码之后,我将其更改为:

    // Foo.h
    namespace my_project
    {
    namespace library
    {
    class Foo_lib
    {
    public:
        int some_value;
        Foo_lib() { /* initialize library */ }
        void bar() { /* do stuff */ }
    private:
        // restrict copy/assignment
        Foo_lib(const Foo_lib&);
        void operator=(const Foo_lib&);
    };
    } // library namespace
    extern library::Foo_lib Foo;
    } // my_project namespace
    
    // Foo.cpp
    #include "Foo.h"
    namespace my_project
    {
    namespace library
    {
        // Foo_lib definitions
    } // library namespace
    library::Foo_lib Foo;
    } // my_project namespace
    
    // main.cpp
    #include "Foo.h"
    using namespace my_project;
    int main()
    {
        int i = Foo.some_value;
        Foo.bar();
        int j = Foo.some_value;
        return 0;
    }
    

    这似乎与以前完全相同。但正如我所说,由于我在外部使用方面仍然模糊,这是否会产生完全相同的不良副作用?

3 个答案:

答案 0 :(得分:3)

这一行特别

static library::Foo Foo;

它会在每个翻译中发出static Foo个副本。不要使用它:) Foo::some_value的结果将等于Foo.h可见的翻译数量,并且它不是线程安全的(这将使用户感到沮丧)。

这一行在链接时会产生多个定义:

int Foo::some_value = 0;

单身人士也很糟糕。在这里搜索@SO会产生很多理由来避免它们。

只需创建普通对象,并向用户提供文档,告诉他们在使用您的库时应该共享对象的原因,以及在哪些情况下。

  

库的用户无需担心初始化。他们不需要调用类似Foo :: init()的东西;在他们的main()中,因为在构造my_project :: Foo时初始化了库:: Foo。这是这里的主要设计约束。用户不应负责初始化库。

对象应该能够根据需要构建自己,而不会引入不可剥离的二进制包。

  

我可以在库中创建各种私有函数来控制它的使用。

这不是你的方法所独有的。

  

无论出于何种原因,用户都可以选择创建此库的其他实例。但是不允许复制。默认情况下会为它们提供一个实例。这是一项要求。

然后你可以强迫你的用户传递Foo作为必要的参数来创建他们所依赖的类型(需要Foo的地方)。

  

我可以使用。语法而不是::。但这是个人风格的事情。

不好。不是线程安全的,然后用户可以严重搞乱你的库的状态。私人数据最好。

答案 1 :(得分:2)

这里有两件事:

  • 如果用户非常喜欢并行化她的代码怎么办?
  • 如果用户希望在静态初始化阶段开始使用您的库,该怎么办?

所以,一次一个。

1。如果用户非常喜欢并行化她的代码怎么办?

在多核处理器时代,图书馆应努力重新进入。 全球状态不好,不同步全局状态更糟糕。

我只是建议你让Foo包含常规属性而不是static个属性,然后由用户决定应该使用多少个并行实例,也许可以解决之一。

如果将Foo传递给所有方法会让人感到尴尬,请查看Facade模式。这里的想法是创建一个用Facade初始化的Foo类,并为您的库提供入口点。

2。如果用户希望在静态初始化阶段开始使用您的库,该怎么办?

静态初始化命令fiasco只是可怕的,而静态破坏命令fiasco(它的兄弟)并不是更好,甚至更难追踪(因为内存在那里没有初始化,所以很难看出会发生什么上)。

因为再一次很难(不可能?)你预测你的库的用法,并且因为在你创建的单例中几乎不可能在静态初始化或破坏期间使用它,所以更简单的事情是至少委托初始化给用户。

如果用户不太愿意在启动和关闭时使用此库,那么您可以提供一个简单的安全措施,以便在首次使用时自动初始化库(如果尚未安装)。

这可以使用本地静态变量以线程安全的方式(*)轻松完成:

class Foo {
public:
  static Foo& Init() { static Foo foo; return foo; }

  static int GetValue() { return Init()._value; }

private:
  Foo(): _value(1) {}
  Foo(Foo const&) = delete;
  Foo& operator=(Foo const&) = delete;

  int _value;
}; // class Foo

请注意,如果您只是决定使用Singleton并转而使用第一个解决方案,那么所有这些粘合剂都是无用的:一个常规对象,只有每个实例状态。

(*)C ++ 11保证线程安全。在C ++ 03(主要在业界使用的版本)中,最好的编译器也能保证,如果需要,请查看文档。

答案 2 :(得分:0)

  

现在问题是,这个解决方案有什么缺点吗?

是。例如,参见静态初始化顺序fiasco的c ++ faq中的这个条目。 http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14 tldr?从本质上讲,您无法控制静态对象(如上面的Foo)的初始化顺序,对顺序的任何假设(例如,使用另一个静态对象初始化)将导致未定义的行为。

在我的应用中考虑此代码。

#include "my_project/library/Foo.h"

static int whoKnowsWhatValueThisWillHave = Foo::some_value;

int main()
{
   return whoKnowsWhatValueThisWillHave;
}

我无法保证从main()返回的内容。

  

无论出于何种原因,用户都可以选择创建此库的其他实例。但是不允许复制。默认情况下会为它们提供一个实例。这是一项要求。

不是,不是......由于您的所有数据都是静态的,因此任何新实例本质上都是指向相同数据的空壳。基本上,你有一份副本。

  

我觉得我正在做一些C ++不应该做的事情,因为Visual Studio的IntelliSense一直在吓唬我,并认为我没有声明my_project :: Foo。可能是因为对象和类都被称为Foo,即使它们位于不同的名称空间中吗?

你是!假设我将此行添加到我的代码中:

使用namespace :: my_project :: library;

'Foo'现在解决了什么?也许这是在标准中定义的,但至少是令人困惑的。

  

我可以使用。语法而不是::。但这是个人风格的事情。

不要对抗语言。如果您想用Python或Java语法编写代码,请使用Python或Java(或Ruby或其他)......

  

我是否应该坚持使用单身设计模式作为替代解决方案?有没有其他解决方案?

是的,Singleton是一个很好的,但你也应该考虑一下你是否真的需要一个单身人士。由于你的例子只是语法,很难说,但也许最好使用依赖注入或类似的东西来最小化/消除类之间的紧密耦合。

希望我没有伤害你的感情:)问问题很好,但显然你已经知道了!