【问题标题】:How to print an object of unknown type如何打印未知类型的对象
【发布时间】:2010-07-22 19:12:47
【问题描述】:

我在 C++ 中有一个模板化容器类,它类似于 std::map(它基本上是 std::map 周围的线程安全包装器)。我想编写一个成员函数来转储有关地图中条目的信息。然而,显然,我不知道地图中对象的类型或它们的键。目标是能够处理基本类型(整数、字符串)以及一些我特别感兴趣的特定类类型。对于任何其他类,我想至少编译,最好做一些有点智能的事情,比如打印对象的地址。到目前为止,我的方法类似于以下(请注意,我实际上并没有编译这个或任何东西......):

template<typename Index, typename Entry>
class ThreadSafeMap
{
    std::map<Index, Entry> storageMap;
    ...
    dumpKeys()
    {
        for(std::map<Index, Entry>::iterator it = storageMap.begin();
            it != storageMap.end();
            ++it)
        {
            std::cout << it->first << " => " << it->second << endl;
        }
    }
    ...
}

这适用于基本类型。我还可以编写自定义流插入函数来处理我感兴趣的特定类。但是,我想不出一个好的方法来处理Index 和/或Entry 是未处理的任意类类型的默认情况.有什么建议吗?

【问题讨论】:

  • 没有什么选择吗?如果一个类不被输出,为什么要尝试输出呢?
  • 对于不支持打印的类什么也不做是可以接受的。目前我收到模板实例化的编译错误,其中一个模板参数不支持打印。对于类不支持打印的情况,我想至少编译而不是崩溃。

标签: c++ stream


【解决方案1】:

您可以提供一个模板化的&lt;&lt; 运算符来捕获未定义自定义输出运算符的情况,因为任何更专业的版本都将优于它。例如:

#include <iostream>

namespace detail
{
    template<typename T, typename CharT, typename Traits>
    std::basic_ostream<CharT, Traits> &
    operator<<(std::basic_ostream<CharT, Traits> &os, const T &)
    {
        const char s[] = "<unknown-type>";
        os.write(s, sizeof(s));
        return os;
    }
}

struct Foo {};

int main()
{
    using namespace detail;
    std::cout << 2 << "\n" << Foo() << std::endl;
    return 0;
}

将输出:

2
<unknown-type>

detailnamespace 是为了防止这个“默认”输出操作符在不需要的地方干扰代码。 IE。你应该只在你的dumpKeys() 方法中使用它(如using namespace detail)。

【讨论】:

  • AFAIK,即使对于实际上已知的类型,它也会打印“未知类型”。请参阅我对 GMan 解决方案的评论。
【解决方案2】:

我最初只有一种更规范的方式来使用Staffan's answer。然而,jpalecek 正确地指出了该方法的一个大缺陷。

就目前而言,如果没有找到 explicit 插入操作符,则模板化插入操作符会启动并定义完美匹配;这破坏了现有隐式转换的任何可能性。

必须做的是使模板插入运算符成为一种转换(同时保持其通用性),因此可以考虑其他转换。一旦没有找到其他人,然后它将被转换为通用插入运算符。

实用程序代码如下:

#include <iosfwd>
#include <memory>

namespace outputter_any_detail
{
    // your generic output function
    template <typename T>
    std::ostream& output_generic(std::ostream& pStream, const T& pX)
    {
        // note: safe from recursion. if you accidentally try 
        // to output pX again, you'll get a compile error
        return pStream << "unknown type at address: " << &pX;
    }

    // any type can be converted to this type,
    // but all other conversions will be 
    // preferred before this one
    class any
    {
    public:
        // stores a type for later output
        template <typename T>
        any(const T& pX) :
        mPtr(new any_holder<T>(pX))
        {}

        // output the stored type generically
        std::ostream& output(std::ostream& pStream) const
        {
            return mPtr->output(pStream);
        }

    private:
        // hold any type
        class any_holder_base
        {
        public:
            virtual std::ostream& output(std::ostream& pStream) const = 0;
            virtual ~any_holder_base(void) {}
        };

        template <typename T>
        class any_holder : public any_holder_base
        {
        public:
            any_holder(const T& pX) :
            mX(pX)
            {}

            std::ostream& output(std::ostream& pStream) const
            {
                return output_generic(pStream, mX);
            }

        private:
            const T& mX;
            any_holder& operator=(const any_holder&);
        };

        std::auto_ptr<any_holder_base> mPtr;
        any& operator=(const any&);
    };

    // hidden so the generic output function
    // cannot accidentally call this fall-back
    // function (leading to infinite recursion)
    namespace detail
    {
        // output a type converted to any. this being a conversion allows
        // other conversions to partake in overload resolution
        std::ostream& operator<<(std::ostream& pStream, const any& pAny)
        {
            return pAny.output(pStream);
        }
    }

    // a transfer class, to allow
    // a unique insertion operator
    template <typename T>
    class outputter_any
    {
    public:
        outputter_any(const T& pX) :
          mX(pX)
          {}

          const T& get(void) const
          {
              return mX;
          }

    private:
        const T& mX;
        outputter_any& operator=(const outputter_any&);
    };

    // this is how outputter_any's get outputted,
    // found outside the detail namespace by ADL
    template <typename T>
    std::ostream& operator<<(std::ostream& pStream, const outputter_any<T>& pX)
    {
        // bring in the fall-back insertion operator
        using namespace detail;

        // either a specifically defined operator,
        // or the generic one via a conversion to any
        return pStream << pX.get();
    }
}

// construct an outputter_any
template <typename T>
outputter_any_detail::outputter_any<T> output_any(const T& pX)
{
    return outputter_any_detail::outputter_any<T>(pX);
}

将其粘贴在诸如"output_any.hpp" 之类的标头中。你就这样使用它:

#include <iostream>
#include "output_any.hpp"    

struct foo {}; 
struct A {}; 
struct B : A {};

std::ostream& operator<<(std::ostream& o, const A&)
{
    return o << "A";
}

int main(void)
{
    foo f;
    int i = 5;
    B b;

    /*

    Expected output:
    unknown type at address: [address]
    5
    [address] 
    A
    */                                       // output via...  
    std::cout << output_any(f) << std::endl; // generic
    std::cout << output_any(i) << std::endl; // int
    std::cout << output_any(&i) << std::endl;// void*
    std::cout << output_any(b) << std::endl; // const A&
}

如果有什么不明白的,请告诉我。

【讨论】:

  • 我喜欢你的回答。我接受了 Staffan 的,因为对于我的应用程序来说,更简单的实现似乎就足够了。不幸的是,我还没有任何赞成票,否则你肯定会得到一个。感谢您的宝贵时间!
  • 我偶然发现了您的解决方案,但发现效果不佳。参见ideone.com/uRBKl,它不能正确处理int*B
  • @jpalecek:虽然我认为您提出了重要的观点,但我想更正您的术语:您说“即使对于实际上已知的类型,这也会打印”未知类型“”但Bint* 都没有插入操作;也就是说,它们不是“已知的”。您的意思是“解决方案,因为它使用模板,因此比任何转换都更喜欢通用解决方案”,这更好地解释了它。 B 可以转换为A,但是模板插入操作匹配更好,int* 可以转换为void*,但模板插入操作再次匹配更好。我会修好它。 :)
  • @jpalecek:应该可以解决它。把模板函数做成模板类+转换,让代码有点冗长,不过也不算太难,反正也不需要经常看。 :)
  • 哦,是的,我本可以意识到这一点的。我试图把它变成可以用于 SFINAE 的东西,并敦促不要使用额外的命名空间——这是一个坏主意。有了额外的命名空间,很容易:ideone.com/xzf7S
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-25
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 2021-04-13
  • 2022-01-21
  • 1970-01-01
相关资源
最近更新 更多