命名空间与静态类

发布于 2024-12-10 20:46:05 字数 2840 浏览 0 评论 0原文

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

// 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. 库的用户不需要担心初始化。他们不需要在 main() 中调用类似 Foo::init(); 的东西,因为 library::Foo 已经初始化了my_project::Foo 构建时。这是这里的主要设计约束。 用户不应该负责初始化库。

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

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

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

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

该解决方案编译良好。我只是担心一旦我的项目规模变得更大,我可能会开始出现名称歧义。此外,我通过创建该库的对象是否浪费了额外的内存?

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

更新:

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

这似乎与以前的效果完全相同。但正如我所说,由于我对 extern 的用法仍然模糊,这是否也会产生完全相同的不良副作用?

For a project I'm working on, I have a bunch of "library classes". These are essentially collections of related functions of values. Some of these libraries need to be "initialized" at run-time. So far, I've been utilizing the design below as a solution:

// 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

Using Foo would be similar to this, as an example:

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

Of course, this example is very simplified, but it gets the point across. This method has four advantages to me:

  1. User of the library doesn't need to worry about initialization. They wouldn't need to call something like Foo::init(); inside their main(), because library::Foo was initialized when my_project::Foo was constructed. This is the main design constraint here. User should not be responsible for initializing the library.

  2. I can create various private functions inside the library to control its use.

  3. The user can create other instances of this library if they choose, for whatever reason. But no copying would be allowed. One instance would be provided for them by default. This is a requirement.

  4. I can use the . syntax instead of ::. But that's a personal style thing.

Now, the question is, are there any disadvantages to this solution? I feel like I'm doing something that C++ wasn't meant to do because Visual Studio's IntelliSense keeps freaking out on me and thinks my_project::Foo isn't declared. Could it be because both the object and the class are called Foo even though they're in different namespaces?

The solution compiles fine. I'm just worried that once my project grows larger in scale, I might start having name ambiguities. Furthermore, am I wasting extra memory by creating an object of this library?

Should I simply stick to the singleton design pattern as an alternative solution? Are there any alternative solutions?

UPDATE:

After reviewing the solutions provided, and jumping around google for various solutions, I stumbled upon extern. I have to say I'm a bit fuzzy on what this keyword really does; I've been fuzzy about it ever since I learned C++. But after tweaking my code, I changed it to this:

// 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;
}

This seems to have the exact same effect as before. But as I said, since I'm still fuzzy on extern usage, would this also have the exact same bad side-effects?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

番薯 2024-12-17 20:46:05

这行代码特别糟糕

static library::Foo Foo;

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

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

int Foo::some_value = 0;

单例也很糟糕。在这里搜索@SO 会产生很多避免它们的理由。

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

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

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

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

这并不是您独有的方法。

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

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

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

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

This line is particularly bad:

static library::Foo Foo;

It emits a static copy of Foo in every translation. Don't use it :) The result of Foo::some_value would be equal to the number of translations the Foo.h was visible to, and it's not thread safe (which will frustrate your users).

This line will result in multiple definitions when linking:

int Foo::some_value = 0;

Singletons are also bad. Searching here @SO will produce a lot of reasons to avoid them.

Just create normal objects, and document to your users why they should share objects when using your library, and in which scenarios.

User of the library doesn't need to worry about initialization. They wouldn't need to call something like Foo::init(); inside their main(), because library::Foo was initialized when my_project::Foo was constructed. This is the main design constraint here. User should not be responsible for initializing the library.

Objects should be able to construct themselves as needed without introducing unstrippable binary baggage.

I can create various private functions inside the library to control its use.

That's not unique to your approach.

The user can create other instances of this library if they choose, for whatever reason. But no copying would be allowed. One instance would be provided for them by default. This is a requirement.

Then you can force your users to pass Foo as a necessary argument to create the types they depend upon (where Foo is needed).

I can use the . syntax instead of ::. But that's a personal style thing.

Not good. Not threadsafe, and the user can then seriously mess up your library's state. Private data is best.

不必在意 2024-12-17 20:46:05

这里发生了两件事:

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

所以,一次一个。

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

请注意,如果您只是决定使用单例并选择第一个解决方案:常规对象,仅具有每个实例的状态。

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

There are two things going on here:

  • What if the user would dearly like to parallelize her code ?
  • What if the user would like to start using your library during the static initialization phase ?

So, one at a time.

1. What if the user would dearly like to parallelize her code ?

In the age of multi-core processors libraries should strive for re-entrancy. Global State is bad, and unsynchronized Global State is even worse.

I would simply recommend for you to make Foo contain regular attributes instead of static ones, it is then up to the user to decide how many instances in parallel should be used, and perhaps settle on one.

If passing a Foo to all your methods would prove awkward, have a look at the Facade pattern. The idea here would be to create a Facade class that is initialized with a Foo and provides entry points to your library.

2. What if the user would like to start using your library during the static initialization phase ?

The static initialization order fiasco is just horrid, and the static destruction order fiasco (its sibling) is no better, and even harder to track down (because the memory is not 0-initialized there, so it's hard to see what's going on).

Since once again it's hard (impossible ?) for you to predict the usage of your library and since any attempt to use it during static initialization or destruction is nigh impossible with a singleton that you would create, the simpler thing to do is to delegate at least initialization to the user.

If the user is unlikely to be willing to use this library at start-up and shut-down, then you may provide a simple safeguard to automatically initialize the library on first use if she didn't already.

This can be accomplished easily, and in a thread-safe manner (*), using local static variables:

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

Note that all this glue is completely useless if you simply decide not to use a Singleton and go for the first solution: a regular object, with per-instance state only.

(*) Thread safety is guaranteed in C++11. In C++03 (the version used primarily in the industry) the best compilers guarantee it as well, check the documentation if required.

聆听风音 2024-12-17 20:46:05

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

是的。例如,请参阅 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,即使它们位于不同的命名空间中?

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

using namespace ::my_project::library;

'Foo' 现在解决了什么?也许标准中已经定义了这一点,但至少,它令人困惑。

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

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

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

是的,单例是一个很好的选择,但是您还应该考虑这里是否真的需要单例。由于您的示例只是语法,所以很难说,但也许使用依赖注入或类似的东西来最小化/消除类之间的紧密耦合会更好。

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

Now, the question is, are there any disadvantages to this solution?

Yes. See for instance, this entry in the c++ faq on the static initialization order fiasco. http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14 tldr? Essentially, you have no control over what order static objects (such as Foo above) get initialized in, any assumptions about the order (eg. initializing one static object with values from another) will result in Undefined Behaviour.

Consider this code in my app.

#include "my_project/library/Foo.h"

static int whoKnowsWhatValueThisWillHave = Foo::some_value;

int main()
{
   return whoKnowsWhatValueThisWillHave;
}

There are no guarantees on what I am returning from main() here.

The user can create other instances of this library if they choose, for whatever reason. But no copying would be allowed. One instance would be provided for them by default. This is a requirement.

Not really, no... Since all of your data is static, any new instances are essentially empty shells pointing to the same data. Basically, you have a copy.

I feel like I'm doing something that C++ wasn't meant to do because Visual Studio's IntelliSense keeps freaking out on me and thinks my_project::Foo isn't declared. Could it be because both the object and the class are called Foo even though they're in different namespaces?

You are! Suppose I add this line to my code:

using namespace ::my_project::library;

what does 'Foo' resolve to now? Maybe this is defined in the standard, but at the very least, it is confusing.

I can use the . syntax instead of ::. But that's a personal style thing.

Don't fight the language. If you want to code in Python or Java syntax, use Python or Java (or Ruby or whatever)...

Should I simply stick to the singleton design pattern as an alternative solution? Are there any alternative solutions?

Yes, the Singleton is a good one, but you should also consider whether you actually need a singleton here. Since your example is only syntactic, it is hard to say, but maybe it would be better to use dependency injection or something similar to minimize/eliminate tight couplings between classes.

Hopefully I haven't hurt your feelings :) It's good to ask questions, but obviously you already know that!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文