шаблонные функции с общими личными данными - PullRequest
1 голос
/ 25 января 2011

Я ищу «лучшие практики» в следующей ситуации: В целом, существует три распространенных способа обмена частными данными между двумя (или более) не-членами функций с различными преимуществами и недостатками:

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

Это неудовлетворительно. Особенно потому, что существуют другие (в данном случае методы) функции, не являющиеся членами, которые должны находиться в той же области (в данном случае в классе), которым не требуется доступ к этим частным данным.

Кто-нибудь знает элегантное решение?

Спасибо за любую помощь. С наилучшими пожеланиями.

Ответы [ 4 ]

1 голос
/ 25 января 2011

К сожалению, эта проблема возникает довольно часто с шаблоном.

Но могу ли я предположить, что вы чрезмерно инженерны здесь?

Правда в том, смотрите ли вы код Loki (от Андрея Александреску) или Boost (особенно пресловутый Дэвид Абрахамс), никто не удосужился обеспечить лучшую конфиденциальность.

Скорее, они просто полагались на соглашение и использовали пространство имен 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

И хоп, я добился локальности в заголовке со злым макросом:)

0 голосов
/ 25 января 2011

Если я правильно понимаю, ваша жалоба / беспокойство заключается в том, что в отличие от шаблонов, с не шаблонами, можно определить тела функций внутри CPP, а не заголовок, и в этом случае они могут получить доступ к неклассовой статистике, «невидимы» для внешнего мира, включая функции-члены, определенные в заголовке. Это все правда.

Однако помните, что ничто не мешает определить другие функции-члены в CPP, и в этом случае они будут в равной степени иметь доступ к статическим данным. Так что на самом деле ситуация ничем не отличается. Ваша жалоба основана на ложной дихотомии.

Если вы действительно хотите запретить что-либо , но s_method0<T>() и s_method1<T>() доступ к s_private, то вы должны поместить их всех в отдельный класс. Это так просто. Это будет иметь место даже для не шаблонов.

0 голосов
/ 25 января 2011

Я играл с разными техниками. Моя идея, используя безымянное пространство имен в заголовочном файле, заключалась в том, чтобы «пометить» «общие» классы как «только заголовочный файл». Конечно, из-за того, что они не содержат публичных участников, вы все равно не сможете делать неприятных вещей. Но я думал, что это будет ближе к цели.

Но я был не прав! Подумав об этом, мне стыдно. Это так логично просто. Этот пример показывает, в чем проблема (весь код для ясности):

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

Что происходит, так это то, что мы заканчиваем двумя неразрешенными внешними явлениями. Так что, компоновщик просто идиот? Нет он не. Размышляя о том, для чего используется безымянное пространство имен, мы знаем, что происходит. Безымянное пространство имен имеет уникальный идентификатор в каждой единице перевода. Таким образом, в нашем 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";
// ...

Я с нетерпением жду любого комментария. С наилучшими пожеланиями.

0 голосов
/ 25 января 2011

Как насчет этого?

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