具有共享私有数据的模板函数

发布于 2024-10-14 09:08:06 字数 2475 浏览 5 评论 0原文

我正在寻找以下情况的“最佳实践”: 一般来说,有三种常见的方法可以在两个(或多个)非成员函数之间共享私有数据,这些方法具有不同的优点和缺点:

// Example 1: using 'static' class

// HPP
namespace foo {
    class bar
    {
    private:
        static const char* const s_private;
        bar();

    public:
        static void s_method0();
        static void s_method1();
    }; /* class bar */
} /* namespace foo */

// CPP
namespace foo {
    const char* const bar::s_private = "why do I need to be visible in HPP?";

    void bar::s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
    void bar::s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; } 
} /* namespace foo */


// Example 2: using unnamed-namespace

// HPP
namespace foo { 
    void bar0(); 
    void bar1();
} /* namespace foo */

// CPP
namespace foo {
    namespace {
        const char* const g_anonymous = "why do I need external linkage?";
    } /* unnamed-namespace */

    void bar0() { std::cout << "bar0 said: " << g_anonymous << std::endl; }
    void bar1() { std::cout << "bar1 said: " << g_anonymous << std::endl; }
} /* namespace foo */


// Example 3: using static keyword in namespace-scope

// HPP
namespace foo { 
    void bar0(); 
    void bar1();
} /* namespace foo */

// CPP
namespace foo {
    static const char* const g_internal = "nobody outside this file can see me and I don't have external linkage";

    void bar0() { std::cout << "bar0 said: " << g_internal << std::endl; }
    void bar1() { std::cout << "bar1 said: " << g_internal << std::endl; }
} /* namespace foo */

我更喜欢“示例 3”,因为它尽可能接近意图。 但现在我在使用模板函数时遇到了一些问题。 “示例 1”似乎是解决此问题的唯一方法:

// HPP
namespace foo {
    class bar
    {
    private:
        static const char* const s_private;
        bar();

    public:
        template<typename T> static void s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
        template<typename T> static void s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; }
    }; /* class bar */
} /* namespace foo */

// CPP
namespace foo {
    const char* const bar::s_private = "why do I need to be visible in HPP?";
} /* namespace foo */

这并不令人满意。特别是因为还有其他(在本例中为方法)非成员函数应该位于相同(在本例中为类)范围内,它们不需要访问此私有数据。

有人知道一个优雅的解决方案吗?

感谢您的任何帮助。 此致。

I'm looking for 'best-practice' in the following situation:
In general, there are three common ways to share private data between two (or more) non-member functions with differential advantages and disadvantages:

// Example 1: using 'static' class

// HPP
namespace foo {
    class bar
    {
    private:
        static const char* const s_private;
        bar();

    public:
        static void s_method0();
        static void s_method1();
    }; /* class bar */
} /* namespace foo */

// CPP
namespace foo {
    const char* const bar::s_private = "why do I need to be visible in HPP?";

    void bar::s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
    void bar::s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; } 
} /* namespace foo */


// Example 2: using unnamed-namespace

// HPP
namespace foo { 
    void bar0(); 
    void bar1();
} /* namespace foo */

// CPP
namespace foo {
    namespace {
        const char* const g_anonymous = "why do I need external linkage?";
    } /* unnamed-namespace */

    void bar0() { std::cout << "bar0 said: " << g_anonymous << std::endl; }
    void bar1() { std::cout << "bar1 said: " << g_anonymous << std::endl; }
} /* namespace foo */


// Example 3: using static keyword in namespace-scope

// HPP
namespace foo { 
    void bar0(); 
    void bar1();
} /* namespace foo */

// CPP
namespace foo {
    static const char* const g_internal = "nobody outside this file can see me and I don't have external linkage";

    void bar0() { std::cout << "bar0 said: " << g_internal << std::endl; }
    void bar1() { std::cout << "bar1 said: " << g_internal << std::endl; }
} /* namespace foo */

I prefer 'Example 3' because it's as close to the intention as it could be.
But now I'm running in some problem's using templated functions. 'Example 1' seems to be the only way to solve this:

// HPP
namespace foo {
    class bar
    {
    private:
        static const char* const s_private;
        bar();

    public:
        template<typename T> static void s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
        template<typename T> static void s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; }
    }; /* class bar */
} /* namespace foo */

// CPP
namespace foo {
    const char* const bar::s_private = "why do I need to be visible in HPP?";
} /* namespace foo */

That's unsatisfying. Especialy because there are other (in this case methods) non-member function which should be in the same (in this case class-) scope, which don't need to access this private data.

Does anybody know an elegant solution?

Thanks for any help.
Best regards.

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

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

发布评论

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

评论(4

情绪失控 2024-10-21 09:08:10

那又怎样呢?

namespace foo {
    namespace detail {
        class shared 
        { 
            template<typename> friend void bar0();
            template<typename> friend void bar1();

            static const char* const m_private; 
        }; /* class shared */
    } /* namespace detail */

    template<typename T> void bar0() { std::cout << "bar0 said: " << detail::shared::m_private << std::endl; }
    template<typename T> void bar1() { std::cout << "bar1 said: " << detail::shared::m_private << std::endl; }
} /* namespace foo */

编辑2:
过时的答案。该代码不起作用!下面发表了解释。

编辑:
在“真实代码”中,我将用未命名的命名空间替换命名空间详细信息。这将带来使用相同名称范围在不同头文件中添加其他共享资源的可能性:

namespace foo {
    template<typename T> void bar0();
    template<typename T> void bar1();
    template<typename T> void bar2();
    template<typename T> void bar3();


    namespace {
        class shared0 
        { 
            template<typename> friend void foo::bar0();
            template<typename> friend void foo::bar1();

            static const char* const m_private0; 
        }; /* class shared0 */

        class shared1
        { 
            template<typename> friend void foo::bar2();
            template<typename> friend void foo::bar3();

            static const char* const m_private1;    
        }; /* class shared1 */
    } /* unnamed */


    template<typename T> void bar0() { std::cout << "bar0 said: " << shared0::m_private0 << std::endl; }
    template<typename T> void bar1() { std::cout << "bar1 said: " << shared0::m_private0 << std::endl; }

    template<typename T> void bar2() { std::cout << "bar0 said: " << shared1::m_private1 << std::endl; }
    template<typename T> void bar3() { std::cout << "bar1 said: " << shared1::m_private1 << std::endl; }
} /* namespace foo */

What about that?

namespace foo {
    namespace detail {
        class shared 
        { 
            template<typename> friend void bar0();
            template<typename> friend void bar1();

            static const char* const m_private; 
        }; /* class shared */
    } /* namespace detail */

    template<typename T> void bar0() { std::cout << "bar0 said: " << detail::shared::m_private << std::endl; }
    template<typename T> void bar1() { std::cout << "bar1 said: " << detail::shared::m_private << std::endl; }
} /* namespace foo */

EDIT 2:
OBSOLETE ANSWER. THIS CODE ISN'T WORKING! POSTED AN EXPLANATION BELOW.

EDIT:
In 'real code' I would replace namespace detail by an unnamed namespace. This would bring up the possibility to add other shared resources in different header files using the same name-scope:

namespace foo {
    template<typename T> void bar0();
    template<typename T> void bar1();
    template<typename T> void bar2();
    template<typename T> void bar3();


    namespace {
        class shared0 
        { 
            template<typename> friend void foo::bar0();
            template<typename> friend void foo::bar1();

            static const char* const m_private0; 
        }; /* class shared0 */

        class shared1
        { 
            template<typename> friend void foo::bar2();
            template<typename> friend void foo::bar3();

            static const char* const m_private1;    
        }; /* class shared1 */
    } /* unnamed */


    template<typename T> void bar0() { std::cout << "bar0 said: " << shared0::m_private0 << std::endl; }
    template<typename T> void bar1() { std::cout << "bar1 said: " << shared0::m_private0 << std::endl; }

    template<typename T> void bar2() { std::cout << "bar0 said: " << shared1::m_private1 << std::endl; }
    template<typename T> void bar3() { std::cout << "bar1 said: " << shared1::m_private1 << std::endl; }
} /* namespace foo */
_失温 2024-10-21 09:08:09

如果我理解正确的话,您的抱怨/担忧是,与模板不同,使用非模板,可以在 CPP 内定义函数体,而不是标头,在这种情况下,它们可以访问非类静态,这些静态是“不可见的” ” 到外界,包括标头中定义的成员函数。这都是真的。

但是,请记住,也可以在 CPP 中定义其他成员函数,在这种情况下,它们同样能够访问静态数据。事实上,情况并没有什么不同。您的投诉是基于错误的二分法。

如果您确实想阻止除 s_method0()s_method1() 之外的任何访问 s_private,那么你必须将它们全部放在一个专门的类中。就这么简单。即使对于非模板也是如此。

If I understand correctly, your complaint/concern is that unlike with templates, with non-templates, one can define the function bodies inside the CPP, rather than the header, in which case they can access non-class statics, which are "invisible" to the outside world, including member functions defined in the header. This is all true.

However, remember that there's nothing stopping one defining other member functions in the CPP as well, in which case, they'd be equally able to access the static data. So really, the situation is no different. Your complaint is based on a false dichotomy.

If you genuinely want to prevent anything but s_method0<T>() and s_method1<T>() accessing s_private, then you must put them all in a dedicated class. It's as simple as that. This would be the case even for non-templates.

橘寄 2024-10-21 09:08:09

我尝试过不同的技术。我的想法是,使用头文件中的未命名命名空间,将“共享”类“标记”为“仅限头文件”。当然,由于它们不包含公共成员,所以你无论如何也不能用它来做一些令人讨厌的事情。但我认为这会更接近意图。

但我错了!想到这里我就羞愧不已。逻辑上是如此简单。这个例子显示了它的问题是什么(为了清晰起见,整个代码):

// header0.hpp
#ifndef HPP_HEADER0_INCLUDED
#define HPP_HEADER0_INCLUDED

#include <iostream>
#include <string>

namespace ns {
    template<typename T> void header0_func0();
    template<typename T> void header0_func1();

    namespace {
        class header0
        {
            template<typename> friend void ns::header0_func0();
            template<typename> friend void ns::header0_func1();

            static std::string s_private;
        }; /* class header0 */
    } /* unnamed */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << header0::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << header0::s_private << std::endl; }
} /* namespace ns */

#endif /* HPP_HEADER0_INCLUDED */ 


// header1.hpp
#ifndef HPP_HEADER1_INCLUDED
#define HPP_HEADER1_INCLUDED

#include <iostream>
#include <string>

namespace ns {
    template<typename T> void header1_func0();
    template<typename T> void header1_func1();

    namespace {
        class header1
        {
            template<typename> friend void ns::header1_func0();
            template<typename> friend void ns::header1_func1();

            static std::string s_private;
        }; /* class header1 */
    } /* unnamed */

    template<typename T> void header1_func0() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
    template<typename T> void header1_func1() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
} /* namespace ns */

#endif /* HPP_HEADER1_INCLUDED */


// source.cpp
#include "header0.hpp"
#include "header1.hpp"

std::string ns::header0::s_private = "header0 private data definition by source.cpp",
            ns::header1::s_private = "header1 private data definition by source.cpp";

namespace {
    // hide private class
    class source
    {
        source();
        ~source();

        static source s_instance;
    };

    source::source() { 
        std::cout << "source.cpp:\n";
        ns::header0_func0<int>();
        ns::header0_func1<int>();
        ns::header1_func0<int>();
        ns::header1_func1<int>();
        std::cout << '\n';
    }

    source::~source() { }
    source source::s_instance;
} /* unnamed */

现在一切似乎都正常。但是如果我们尝试在其他翻译单元中使用我们的标题会发生什么?让我们看一下:

// main.cpp
#include "header0.hpp"
#include "header1.hpp"

int main()
{   
    std::cout << "main.cpp:\n";
    ns::header0_func0<int>();
    ns::header0_func1<int>();
    ns::header1_func0<int>();
    ns::header1_func1<int>();
    std::cout << '\n';

    return 0;
}

发生的情况是,我们以 2 个未解决的外部问题结束。那么,链接器只是一个白痴吗?不,他不是。想想未命名命名空间的用途,我们就知道发生了什么。未命名的命名空间在每个翻译单元中都有唯一的标识符。因此,在我们的 main.cpp 中,链接器不知道 source.cpp 中私有数据的定义。
那么,如果我们在 main.cpp 中定义这些私有数据(只是为了让问题变得更严重),会发生什么?

// main.cpp
#include "header0.hpp"
#include "header1.hpp"

std::string ns::header0::s_private = "header0 private data definition by main.cpp",
            ns::header1::s_private = "header1 private data definition by main.cpp";

int main()
{   
    std::cout << "main.cpp:\n";
    ns::header0_func0<int>();
    ns::header0_func1<int>();
    ns::header1_func0<int>();
    ns::header1_func1<int>();
    std::cout << '\n';

    return 0;
}

现在,一切都在“正确”地编译和链接——或者更确切地说,看起来是这样。
这是该程序的控制台输出:

source.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp

main.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp

这意味着:如果您正在寻找未定义的行为,那么您就在这里。
换句话说:基于上面的解释:不要在头文件中使用未命名的命名空间来封装私有共享数据。

最后一个问题是,“解决方案是什么?”
如果您不想使用“静态”(实用程序)类,您应该更喜欢我第一个发布的解决方案(仅更改了代码):

// header0.hpp
// ...
namespace ns {
    // ...  
    namespace detail { 
        class header0 { /*...*/ };
    } /* namespace detail */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header0::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header0::s_private << std::endl; }
} /* namespace ns */
// ...

// header1.hpp
// ...
namespace ns {
    // ...  
    namespace detail { 
        class header1 { /*...*/ };
    } /* namespace detail */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header1::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header1::s_private << std::endl; }
} /* namespace ns */
// ...

// source.cpp
// ...
std::string ns::detail::header0::s_private = "header0 private data definition by source.cpp",
            ns::detail::header1::s_private = "header1 private data definition by source.cpp";
// ...

我期待任何评论。此致。

I had played around with the different techniques. My idea, using the unnamed namespace in the header file, was to 'mark' the 'shared'-classes as 'header-file-only'. Sure, due to the fact they aren't containing public members you cannot make nasty things anyway with it. But I thought it would be more close to the intention.

But I was wrong! After thinking about this I'm in shame. It's so logically simple. This example is showing what's the problem with it (entire code for clearness):

// header0.hpp
#ifndef HPP_HEADER0_INCLUDED
#define HPP_HEADER0_INCLUDED

#include <iostream>
#include <string>

namespace ns {
    template<typename T> void header0_func0();
    template<typename T> void header0_func1();

    namespace {
        class header0
        {
            template<typename> friend void ns::header0_func0();
            template<typename> friend void ns::header0_func1();

            static std::string s_private;
        }; /* class header0 */
    } /* unnamed */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << header0::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << header0::s_private << std::endl; }
} /* namespace ns */

#endif /* HPP_HEADER0_INCLUDED */ 


// header1.hpp
#ifndef HPP_HEADER1_INCLUDED
#define HPP_HEADER1_INCLUDED

#include <iostream>
#include <string>

namespace ns {
    template<typename T> void header1_func0();
    template<typename T> void header1_func1();

    namespace {
        class header1
        {
            template<typename> friend void ns::header1_func0();
            template<typename> friend void ns::header1_func1();

            static std::string s_private;
        }; /* class header1 */
    } /* unnamed */

    template<typename T> void header1_func0() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
    template<typename T> void header1_func1() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
} /* namespace ns */

#endif /* HPP_HEADER1_INCLUDED */


// source.cpp
#include "header0.hpp"
#include "header1.hpp"

std::string ns::header0::s_private = "header0 private data definition by source.cpp",
            ns::header1::s_private = "header1 private data definition by source.cpp";

namespace {
    // hide private class
    class source
    {
        source();
        ~source();

        static source s_instance;
    };

    source::source() { 
        std::cout << "source.cpp:\n";
        ns::header0_func0<int>();
        ns::header0_func1<int>();
        ns::header1_func0<int>();
        ns::header1_func1<int>();
        std::cout << '\n';
    }

    source::~source() { }
    source source::s_instance;
} /* unnamed */

By now everything seems to be OK. But what happen's if we try to use our headers in other translation units? Let's take a look:

// main.cpp
#include "header0.hpp"
#include "header1.hpp"

int main()
{   
    std::cout << "main.cpp:\n";
    ns::header0_func0<int>();
    ns::header0_func1<int>();
    ns::header1_func0<int>();
    ns::header1_func1<int>();
    std::cout << '\n';

    return 0;
}

What happens is, that we are ending with 2 unresolved externals. So, is the linker just an idiot? No, he is not. Thinking about what are unnamed namespace used for, we know what's going on. An unnamed namespace has an unique identifier in each translation unit. Thus, in our main.cpp, the linker doesn't know the definition of our private data in source.cpp.
So, what happens if we define this private data in main.cpp - just to bring matters to a head - ?

// main.cpp
#include "header0.hpp"
#include "header1.hpp"

std::string ns::header0::s_private = "header0 private data definition by main.cpp",
            ns::header1::s_private = "header1 private data definition by main.cpp";

int main()
{   
    std::cout << "main.cpp:\n";
    ns::header0_func0<int>();
    ns::header0_func1<int>();
    ns::header1_func0<int>();
    ns::header1_func1<int>();
    std::cout << '\n';

    return 0;
}

Now, everything is compiling and getting linked 'correctly' - or rather, it seems so.
This is the console output of that program:

source.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp

main.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp

That means: If undefined behavior is what you are looking for, here you are.
In other words: Based on the explanation above: Don't use unnamed namespace in header files to encapsulate private shared data.

And the last question is, "what's the solution?"
If you don't want to use 'static' (utility) classes, you should prefer my first posted solution (only changed code):

// header0.hpp
// ...
namespace ns {
    // ...  
    namespace detail { 
        class header0 { /*...*/ };
    } /* namespace detail */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header0::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header0::s_private << std::endl; }
} /* namespace ns */
// ...

// header1.hpp
// ...
namespace ns {
    // ...  
    namespace detail { 
        class header1 { /*...*/ };
    } /* namespace detail */

    template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header1::s_private << std::endl; }
    template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header1::s_private << std::endl; }
} /* namespace ns */
// ...

// source.cpp
// ...
std::string ns::detail::header0::s_private = "header0 private data definition by source.cpp",
            ns::detail::header1::s_private = "header1 private data definition by source.cpp";
// ...

I'm looking forward to any comment. Best regards.

春夜浅 2024-10-21 09:08:08

不幸的是,这是模板经常出现的一个问题。

但我可以建议你在这里过度设计吗?

事实是,无论您查看 Loki 代码(由 Andrei Alexandrescu 编写)还是 Boost 代码(尤其是臭名昭著的 David Abrahams),没有人真正费心提供更好的隐私。

相反,他们只是简单地依赖约定并使用 Private 命名空间 (Loki) 或 detail 命名空间(Boost,有时使用更长、更具描述性的名称来防止冲突)。

这很烦人,但在实践中你能做的并不多......虽然我实际上有一个针对你的具体问题的解决方案;)

// Evil solution!

#ifdef MY_SUPER_MACRO
#  error "MY_SUPER_MACRO is already defined!"
#endif

#define MY_SUPER_MACRO "Some string"

template <typename T> void foo() { std::cout << "foo - " MY_SUPER_MACRO "\n"; }
template <typename T> void bar() { std::cout << "bar - " MY_SUPER_MACRO "\n"; }

#undef MY_SUPER_MACRO

并且跳跃,我用一个邪恶的宏在标头中实现了局部性:)

This is, somewhat unfortunately, an issue that springs quite often with template.

But may I suggest that you are over-engineering here ?

The truth is, whether you look at Loki code (by Andrei Alexandrescu) or Boost code (infamous David Abrahams notably), no-one really bothered to provide a better privacy.

Rather, they simply relied on convention and used a Private namespace (Loki) or a detail namespace (Boost, with sometimes a longer and more descriptive name to prevent clashes).

It's annoying, but there is not much you can do in practice.... though I actually have a solution for your specific problem ;)

// Evil solution!

#ifdef MY_SUPER_MACRO
#  error "MY_SUPER_MACRO is already defined!"
#endif

#define MY_SUPER_MACRO "Some string"

template <typename T> void foo() { std::cout << "foo - " MY_SUPER_MACRO "\n"; }
template <typename T> void bar() { std::cout << "bar - " MY_SUPER_MACRO "\n"; }

#undef MY_SUPER_MACRO

And hop, I achieved locality in a header with an evil macro :)

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