4

请考虑以下代码:

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

struct C : public B { int go() { return 2; } };

int main()
{
    B b;
    B &b_ref = b;

    return b_ref.go();
}

在 GCC 4.4.1(使用-O2)下,对的调用B::go()被内联(即,没有发生虚拟调度)。这意味着编译器承认a_ref确实指向了一个B类型变量。B引用可用于指向 a ,C但编译器足够聪明,可以预见到情况并非如此,因此它完全优化了函数调用,内联函数。

伟大的!这是一个令人难以置信的优化。

但是,那么,为什么 GCC 在以下情况下不做同样的事情呢?

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

struct C : public B { int go() { return 2; } };

int main()
{
    B b;
    A &b_ref = b;

    return b_ref.go(); // B::go() is not inlined here, and a virtual dispatch is issued
}

有任何想法吗?其他编译器呢?这种优化常见吗?(我对这种编译器洞察力很陌生,所以我很好奇)

如果第二种情况有效,我可以创建一些非常棒的模板,例如:

template <typename T>
class static_ptr_container
{
public:
    typedef T st_ptr_value_type;

    operator T *() { return &value; }
    operator const T *() const { return &value; }

    T *operator ->() { return &value; }
    const T *operator ->() const { return &value; }

    T *get() { return &value; }
    const T *get() const { return &value; }

private:
    T value;
};

template <typename T>
class static_ptr
{
public:
    typedef static_ptr_container<T> container_type;
    typedef T st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<static_ptr_container<T>>
{
public:
    typedef static_ptr_container<T> container_type;
    typedef typename container_type::st_ptr_value_type st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *()  { return container->get(); }
    inline st_ptr_value_type *operator ->()  { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<const T>
{
public:
    typedef const static_ptr_container<T> container_type;
    typedef const T st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

template <typename T>
class static_ptr<const static_ptr_container<T>>
{
public:
    typedef const static_ptr_container<T> container_type;
    typedef typename container_type::st_ptr_value_type st_ptr_value_type;

    static_ptr() : container(NULL) {}
    static_ptr(container_type *c) : container(c) {}

    inline operator st_ptr_value_type *() { return container->get(); }
    inline st_ptr_value_type *operator ->() { return container->get(); }

private:
    container_type *container;
};

在许多情况下,这些模板可用于避免虚拟调度:

// without static_ptr<>
void func(B &ref);

int main()
{
    B b;
    func(b); // since func() can't be inlined, there is no telling I'm not
             // gonna pass it a reference to a derivation of `B`

    return 0;
}

// with static_ptr<>
void func(static_ptr<B> ref);

int main()
{
    static_ptr_container<B> b;
    func(b); // here, func() could inline operator->() from static_ptr<> and
             // static_ptr_container<> and be dead-sure it's dealing with an object
             // `B`; in cases func() is really *only* meant for `B`, static_ptr<>
             // serves both as a compile-time restriction for that type (great!)
             // AND as a big runtime optimization if func() uses `B`'s
             // virtual methods a lot -- and even gets to explore inlining
             // when possible

    return 0;
}

实施它是否可行?(不要继续说这是一个微优化,因为它很可能是一个巨大的优化..)

- 编辑

我只是注意到问题与static_ptr<>我暴露的问题无关。指针类型被保留,但它仍然没有内联。我猜 GCC 并没有深入到找出 static_ptr_container<>::value 不是引用也不是指针的程度。对于那个很抱歉。但这个问题仍然没有答案。

- 编辑

我已经制定了一个static_ptr<>真正有效的版本。我也稍微改了一下名字:

template <typename T>
struct static_type_container
{
    // uncomment this constructor if you can't use C++0x
    template <typename ... CtorArgs>
    static_type_container(CtorArgs ... args)
            : value(std::forward<CtorArgs>(args)...) {}

    T value; // yes, it's that stupid.
};

struct A
{
    virtual ~A() {}
    virtual int go() = 0;
};

struct B : public A { int go() { return 1; } };

inline int func(static_type_container<Derived> *ptr)
{
    return ptr->value.go(); // B::go() gets inlined here, since
                            // static_type_container<Derived>::value
                            // is known to be always of type Derived
}

int main()
{
    static_type_container<Derived> d;
    return func(&d); // func() also gets inlined, resulting in main()
                     // that simply returns 1, as if it was a constant
}

唯一的弱点是用户必须访问ptr->value才能获取实际对象。重载operator ->()在 GCC 中不起作用。任何返回对实际对象的引用的方法(如果它是内联的)都会破坏优化。太遗憾了..

4

1 回答 1

2

这不是一个明确的答案,但我想我还是可以发布它,因为它可能对某些人有用。

评论者Julio Guerra指出了一个名为静态 C++ 面向对象编程(SCOOP)的 C++ 习语(他们在论文中称其为“范式”,但我认为这有点过分了)。我将发布此内容以提高 SCOOP 的知名度。

SCOOP 的发明是为了让 C++ 程序员通过在 C++ 中很好地协同工作来充分利用 OOP 和 GP 世界。它主要针对科学编程,因为它可以带来性能提升,并且可以用来提高代码的表达能力。

SCOOP 使 C++ 泛型类型模拟了传统面向对象编程的所有方面——静态的。这意味着模板方法具有特征,例如,能够正确重载和(显然)发出比通常由临时模板函数引起的错误消息更正确的错误消息。

它也可以用来做一些有趣的技巧,比如条件继承。

我试图完成的static_ptr<>正是一种静态面向对象。SCOOP 将其提升到一个全新的水平。

对于那些感兴趣的人,我发现了两篇关于此的论文:静态 C++ 面向对象编程 (SCOOP) 范式混合了传统 OOP 和通用编程的好处和语义驱动的通用性:静态 C++ 面向对象编程范式的续集(独家新闻 2)

但是,这个习语并非没有自己的缺点:它是那些不常见的事情之一,应该是你最后的手段,因为人们很可能很难弄清楚你做了什么,等等。你的代码也会变得更加冗长,你是可能会发现自己无法做你认为可能的事情。

不过,我确信它在某些情况下仍然有用,更不用说真正的乐趣了。

快乐的模板黑客。

于 2010-12-08T01:37:11.260 回答