繁体   English   中英

命名空间与静态类

[英]Namespaces vs. Static Classes

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

// 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();类的东西Foo::init(); main()内部,因为在构造my_project::Foo时初始化了library::Foo 这是这里的主要设计约束。 用户应该负责初始化库。

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

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

  4. 我可以使用. 语法而不是:: 但这是个人风格。

现在的问题是,该解决方案是否有缺点? 我觉得我正在做C ++没打算做的事情,因为Visual Studio的IntelliSense一直吓到我了,并认为未声明my_project::Foo 可能是因为即使对象和类都位于不同的命名空间中也被称为Foo吗?

解决方案编译良好。 我只是担心,一旦我的项目规模变大,我可能会开始感到名字模棱两可。 此外,是否通过创建该库的对象来浪费额外的内存?

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

更新:

在查看了提供的解决方案之后,并在google上寻找各种解决方案后,我偶然发现了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;
}

这似乎具有与以前完全相同的效果。 但是正如我说的那样,由于我仍然对外部用法不了解,这还会产生完全相同的不良影响吗?

这条线特别糟糕

static library::Foo Foo;

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

链接时,此行将导致多个定义:

int Foo::some_value = 0;

单身人士也很糟糕。 在此处搜索@SO会产生很多避免它们的原因。

只需创建普通对象,并向用户说明为什么他们在使用您的库时以及在哪种情况下应该共享对象。

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

对象应该能够根据需要构造自身,而不会引入不可分割的二进制包。

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

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

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

然后,您可以强制用户将Foo作为必需的参数传递,以创建他们依赖的类型(需要Foo的位置)。

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

不好。 不是线程安全的,然后用户可能会严重弄乱您的库的状态。 私人数据是最好的。

这里有两件事:

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

所以,一次。

1.如果用户非常想并行化她的代码怎么办?

在多核处理器时代,库应努力实现重新进入。 全球状态很糟糕, 不同步的全球状态甚至更糟。

我只是简单地建议您使Foo包含常规属性而不是static属性,然后由用户决定应使用多少个并行实例,甚至可以确定一个。

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

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

静态初始化顺序惨败是可怕的,而静态销毁顺序惨败(它的同级兄弟)没有更好,甚至更难追踪(因为那里的内存没有被0初始化,所以很难看到发生了什么)。

由于您再次很难(不可能?)来预测库的使用,并且由于在静态初始化或销毁过程中使用创建的单例几乎不可能尝试使用它,因此更简单的方法是在最少初始化给用户。

如果用户不太可能在启动和关闭时使用该库,那么您可以提供一种简单的保护措施,以在首次使用时自动初始化该库(如果尚未使用的话)。

使用局部静态变量,可以轻松且以线程安全的方式(*)完成此操作:

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(主要用于行业中的版本)中,最好的编译器也可以保证它,如果需要,请查阅文档。

现在的问题是,该解决方案是否有缺点?

是。 例如,请参见c ++常见问题解答中有关静态初始化顺序惨败的条目。 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吗?

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

使用命名空间:: my_project :: library;

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

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

不要和语言打架。 如果要使用Python或Java语法进行编码,请使用Python或Java(或Ruby或其他)...

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

是的,Singleton是一个不错的选择,但是您还应该考虑这里是否真的需要一个Singleton。 由于您的示例仅是语法上的,所以很难说,但是最好使用依赖注入或类似的方法来最小化/消除类之间的紧密耦合。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM