【问题标题】:Getting class name as a string in static method in C++在 C++ 中的静态方法中将类名作为字符串获取
【发布时间】:2016-06-11 13:45:23
【问题描述】:

假设我们严格使用 Clang。没有使用其他编译器。另请注意,Clang 支持 CXX ABI。

我们使用的是 C++14。

通常,我们会像这样得到解组的类名:

#include <cxxabi.h>

class GoodClass {
public:
    virtual const char *foo() const noexcept;
}

const char *
GoodClass::foo() const noexcept
{
    //  Naive implementation, not gonna' check any errors and stuff.
    int32_t status = 0;

    return abi::__cxa_demangle(typeid(*this).name(), 0, 0, &status);
}

当我们需要这个类的公共子类的类名时,这个方法会有所帮助:

class SomeSubclassOfGoodClass : public GoodClass { }

SomeSubclassOfGoodClass object;
std::cout << object.foo(); //  prints "SomeSubclassOfGoodClass"

但是,在静态方法中,我们不能使用this,因为没有实例。因此,不可能将对象提供给typeid 指令。

示例方法很好用(具有多态性),但是它需要一个实例来操作。这会涉及到面向对象的问题(比如构造函数)。

遇到这种情况你会怎么做?

感谢您的关注。

【问题讨论】:

  • 我认为您缺少“clang”标签。
  • 我确实读过这个问题,Leviathalon。对于返回您所寻求的信息的静态函数,除了访问该类型的现有对象(在本地创建或通过其他方式访问)和/或将有关该类型的某些特定假设硬编码到功能。您要求第三个选项,它根本不存在。您希望它存在并没有实现。
  • @Leviathlon:.NET 框架无法从静态类成员返回多态类型信息。我不知道那应该是什么,也不知道为什么你认为这很有用。
  • 分头发,嗯?我的措辞是我的意思:没有一种针对 .NET 框架的语言可以从静态类成员中推断出动态类型。考虑到它甚至可以在没有对象的情况下调用,那么动态类型应该是什么?这个问题真的没有意义,对不起。
  • 调用X::foo()时你在说什么对象?

标签: c++ polymorphism clang c++14 static-methods


【解决方案1】:

使用 demangle 需要做一些工作。目前你有内存泄漏。

这是解决这个问题的一种方法:

#include <cxxabi.h>
#include <memory>
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <cassert>
#include <stdexcept>

struct demangled_string
{
    using ptr_type = std::unique_ptr<char, void(*)(void*)>;
    demangled_string(ptr_type&& ptr) noexcept;
    const char* c_str() const;
    operator std::string() const;

    std::ostream& write(std::ostream& os) const;
private:
    ptr_type _ptr;
};

inline std::ostream& operator<<(std::ostream& os, const demangled_string& str)
{
    return str.write(os);
}

inline std::string operator+ (std::string l, const demangled_string& r) {
    return l + r.c_str();
}

inline std::string operator+(const demangled_string& l, const std::string& r)
{
    return std::string(l) + r;
}

demangled_string demangle(const char* name);
demangled_string demangle(const std::type_info& type);
demangled_string demangle(std::type_index type);

template<class T>
demangled_string demangle(T* p) {
    return demangle(typeid(*p));
}

template<class T>
demangled_string demangle()
{
    return demangle(typeid(T));
}


// implementation

demangled_string::demangled_string(ptr_type&& ptr) noexcept
: _ptr(std::move(ptr))
{}

std::ostream& demangled_string::write(std::ostream& os) const
{
    if (_ptr) {
        return os << _ptr.get();
    }
    else {
        return os << "{nullptr}";
    }
}

const char* demangled_string::c_str() const
{
    if (!_ptr)
    {
        throw std::logic_error("demangled_string - zombie object");
    }
    else {
        return _ptr.get();
    }
}

demangled_string::operator std::string() const {
    return std::string(c_str());
}


demangled_string demangle(const char* name)
{
    using namespace std::string_literals;

    int status = -4;

    demangled_string::ptr_type ptr {
        abi::__cxa_demangle(name, nullptr, nullptr, &status),
        std::free
    };

    if (status == 0) return { std::move(ptr) };

    switch(status)
    {
        case -1: throw std::bad_alloc();
        case -2: {
            std::string msg = "invalid mangled name~";
            msg += name;
            auto p = (char*)std::malloc(msg.length() + 1);
            strcpy(p, msg.c_str());
            return demangled_string::ptr_type { p, std::free };
        }
        case -3:
            assert(!"invalid argument sent to __cxa_demangle");
            throw std::logic_error("invalid argument sent to __cxa_demangle");
        default:
            assert(!"PANIC! unexpected return value");
            throw std::logic_error("PANIC! unexpected return value");
    }
}

demangled_string demangle(const std::type_info& type)
{
    return demangle(type.name());
}

demangled_string demangle(std::type_index type)
{
    return demangle(type.name());
}

std::string method(const demangled_string& cls, const char* method)
{
    return std::string(cls) + "::" + method;
}

// test

class test_class
{
    using this_class = test_class;

    static auto classname() { return demangle<this_class>(); }

public:
    static void test1() {
        std::cout << method(demangle<this_class>(), __func__) << std::endl;
        std::cout << method(classname(), __func__) << std::endl;
    }

    void test2() {
        std::cout << method(demangle(this), __func__) << std::endl;
        std::cout << method(classname(), __func__) << std::endl;
    }

};

int main()
{
    test_class t;
    t.test1();
    t.test2();
}

预期输出:

test_class::test1
test_class::test1
test_class::test2
test_class::test2

【讨论】:

    【解决方案2】:

    typeid 运算符也可以应用于类型,而不仅仅是表达式:typeid(GoodClass) 应该在您无法访问 this 时工作。

    编辑:没有实例,您需要转向静态多态性。您可以在基类 Identifiable&lt;X&gt; 中混合使用,它具有带有您上面建议的代码的静态方法,但使用 typeid(X) 代替。你的类需要扩展这个类,将自己作为模板参数传递(奇怪的递归模板模式),但不可能确保一个类这样做:

    class C : public Identifiable<C> {}; // method returns C
    class D : public Identifiable<C> {}; // also returns C
    

    【讨论】:

    • 没错,但是我们这里需要多态性。所以你的实现建议不会给我们子类名称。它只会给出实现类,这里是GoodClass。我们应该为typeid 提供一些类占位符,因此它将给我们类名。
    • 另外,另一个实现也将不胜感激。
    • 静态方法没有实例供您发现类名...您可以做的最好的事情是使其成为实例方法或将静态方法放在 mixin 模板基类中CRTP:class X : public Identifiable&lt;X&gt;,其中基类使用typeid(X)
    • @JavierMartín 这也是我的想法。但是,在框架设计中,这可能会被滥用。我认为我们已经接近 C++ 的 OO 能力了。
    • @Leviathlon 在 C++ 中,一切都可能被滥用。像其他评论者一样,我没有看到使用键入 Subclass::foo() 只是为了获取字符串“Subclass”。没有多态实例,我对我没有意义。如果您正在考虑将其用于插件架构,您确定拥有一个带有一些 X 子类需要使用的注册表的 XFactory 不是更好吗?
    猜你喜欢
    • 1970-01-01
    • 2013-08-19
    • 2020-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 1970-01-01
    相关资源
    最近更新 更多