如何打印未知类型的对象

时间:2021-11-26 04:51:10

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). 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...):

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

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. Any suggestions?

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

2 个解决方案

#1


14  

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:

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

#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. I.e. you should only use it (as in using namespace detail) in your dumpKeys() method.

详细命名空间用于防止此“默认”输出操作符干扰除需要之外的位置的代码。即您应该只在dumpKeys()方法中使用它(如在使用命名空间详细信息中)。

#2


11  

I originally had just a more canonical way of using Staffan's answer. However, jpalecek correctly pointed out a large flaw with the approach.

我最初只是采用了更为规范的方式来使用Staffan的答案。然而,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". And you use it as such:

把它贴在像“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&
}

Let me know if something doesn't make sense.

如果某些事情没有意义,请告诉我。

#1


14  

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:

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

#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. I.e. you should only use it (as in using namespace detail) in your dumpKeys() method.

详细命名空间用于防止此“默认”输出操作符干扰除需要之外的位置的代码。即您应该只在dumpKeys()方法中使用它(如在使用命名空间详细信息中)。

#2


11  

I originally had just a more canonical way of using Staffan's answer. However, jpalecek correctly pointed out a large flaw with the approach.

我最初只是采用了更为规范的方式来使用Staffan的答案。然而,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". And you use it as such:

把它贴在像“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&
}

Let me know if something doesn't make sense.

如果某些事情没有意义,请告诉我。