简体   繁体   English

如何打印未知类型的对象

[英]How to print an object of unknown type

I have a templatized container class in C++ which is similar to a std::map (it's basically a thread-safe wrapper around the std::map). 我在C ++中有一个模板化的容器类,类似于std :: map(它基本上是std :: map的一个线程安全的包装器)。 I'd like to write a member function which dumps information about the entries in the map. 我想编写一个成员函数来转储有关地图中条目的信息。 Obviously, however, I don't know the type of the objects in the map or their keys. 但是,显然,我不知道地图中对象的类型或键。 The goal is to be able to handle the basic types (integers, strings) and also some specific class types that I am particularly interested in. For any other class, I'd like to at least compile, and preferably do something somewhat intelligent, such as print the address of the object. 目标是能够处理基本类型(整数,字符串)以及我特别感兴趣的一些特定类类型。对于任何其他类,我想至少编译,并且最好做一些有点聪明的事情,比如打印对象的地址。 My approach so far is similar to the following (please note, I didn't actually compile this or anything...): 到目前为止我的方法类似于以下(请注意,我实际上没有编译这个或任何东西......):

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

This works for basic types. 这适用于基本类型。 I can also write custom stream insertion functions to handle specific classes I'm interested in. However, I can't figure out a good way to handle the default case where Index and/or Entry is an unhandled arbitrary class type. 我也可以编写自定义流插入函数来处理我感兴趣的特定类。但是,我无法找到一种处理Index和/或Entry是未处理的任意类类型的默认情况的好方法。 Any suggestions? 有什么建议?

You can provide a templated << operator to catch the cases where no custom output operator is defined, since any more specialized versions will be preferred over it. 您可以提供模板化的<<运算符来捕获未定义自定义输出运算符的情况,因为任何更专业的版本都将优先于它。 For example: 例如:

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

will output: 将输出:

2
<unknown-type>

The detail namespace is there to keep this "default" output operator from interfering with code in places other than where it is needed. detail namespace用于防止此“默认”输出操作符干扰除需要之外的位置的代码。 Ie you should only use it (as in using namespace detail ) in your dumpKeys() method. 即你应该只在dumpKeys()方法中使用它(如using namespace detail )。

I originally had just a more canonical way of using Staffan's answer . 我原本只有一种更规范的方式来使用Staffan的答案 However, jpalecek correctly pointed out a large flaw with the approach. 然而,jpalecek正确地指出了这种方法的一个大缺陷。

As it stood, if no explicit insertion operator is found, the templated insertion operator kicks in and defines a perfect match; 如上所述,如果没有找到明确的插入操作符,模板化插入操作符就会启动并定义完美匹配; this destroys any possibility for existing implicit conversions. 这会破坏现有隐式转换的任何可能性。

What must be done is make that template insertion operator a conversion (while maintain it's generality), so other conversions can be considered. 必须做的是使模板插入操作符成为转换(同时保持其一般性),因此可以考虑其他转换。 Once no others are found, then it will be converted to the generic insertion operator. 一旦找不到其他人, 那么它将被转换为通用插入运算符。

The utility code is as such: 实用程序代码如下:

#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);
}

Stick it in some header like "output_any.hpp" . 把它"output_any.hpp"在像"output_any.hpp"这样的标题中。 And you use it as such: 你这样使用它:

#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&
}

Let me know if something doesn't make sense. 如果事情没有意义,请告诉我。

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

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