为什么这段代码能够编译通过?(C++ 模板问题)

3
我正在使用类模板编写一个广义容器,其中有一个限制(策略),即存储在容器中的项应该派生自特定的基类。
以下是该类模板的定义。
// GenericContainer.hpp
// --------------------------------------
class ContainerItem
{
protected:
    virtual ContainerItem& getInvalid() = 0;

public:
    virtual ~ContainerItem();
    bool isValid() const;
};


template<typename D, typename B>
class IsDerivedFrom
{
    static void Constraints(D* p)
    {
        B* pb = p; // this line only works if 'D' inherits 'B'
        pb = p; // suppress warnings about unused variables
    }

protected:
    void IsDerivedFrom2() { void(*p)(D*) = Constraints; }
};


// Force it to fail in the case where B is void
template<typename D>
class IsDerivedFrom<D, void>
{
    void IsDerivedFrom2() { char* p = (int*)0; /* error */ }
};



template <class T>
class GenericContainer : public IsDerivedFrom<T, ContainerItem>
{
private:
    typedef std::vector<T> TypeVect;
    void addElement(const T& elem);

    TypeVect m_elems;

public:
    unsigned int size() const;
    T& elementAt(const unsigned int pos);
    const T& elementAt(const unsigned int pos) const;
};


template <class T>
void GenericContainer<T>::addElement(const T& elem)
{
    m_elems.push_back(elem);
}

template <class T>
unsigned int GenericContainer<T>::size() const
{
    return m_elems.size();
}

template <class T>
T& GenericContainer<T>::elementAt(const unsigned int pos)
{
    unsigned int maxpos = m_elems.size();
    if (pos < maxpos)
        return m_elems[pos];
    return T::getInvalid();
}


template <class T>
const T& GenericContainer<T>::elementAt(const unsigned int pos) const
{
    unsigned int maxpos = m_elems.size();
    if (pos < maxpos)
        return m_elems[pos];
    return T::getInvalid();
}


// Class to be contained (PURPOSELY, does not derive from ContainerItem)
// Data.hpp
//----------------------------------------------------------------

class Data
{ /* implem details */};


// Container for Data items
// Dataset.h
// ----------------------------------------------------------------------------

#include "GenericContainer.hpp"
#include "Data.hpp"

class Dataset: public GenericContainer<Data>
{
public:
   Data& getInvalid();
};


// C++ source
// -----------------------------------------------------------
#include "Dataset.hpp"

Dataset ds;

有人可以解释一下为什么上面的代码可以编译吗?
编辑:
上述代码不应该编译,原因有两个:
1.类“Data”没有从“ContainerItem”派生,但它可以存储在GenericContainer中(如类Dataset所示)。顺便说一句,由于Omifarious和jdv提供的答案,这个问题现在已经得到解决。
2.类“Data”没有实现ABC ContainerItem中声明的纯虚方法——使用下面答案中推荐的修复方法,第一个问题(强制执行策略)得到了解决,然而编译器却没有注意到Data没有实现ContainerItem“接口”的getInvalid()方法。为什么编译器会忽视这个明显的错误呢?
顺便说一下,编译器和操作系统的详细信息为: g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3

1
如果您能解释一下为什么您认为它不应该编译,那将有助于我们理解您的问题。 - Adrian Grigore
这个问题不应该因为代码太长或者你不理解而被关闭。这个人犯了一个有趣的错误,这个问题值得回答。 - Omnifarious
你是否见过BOOST_STATIC_ASSERT和类似的机制?(C++0x将其标准化为“static_assert”。) - Fred Nurk
@Omnifarious:我没有投票关闭它,无需激动不安。 - Adrian Grigore
@Omnifarious:特别是当你需要深入了解模板问题时,它们必然会变得冗长。 :) - Matthieu M.
@Adrian Grigore - 我的评论是针对投票关闭的人,而不是你。 - Omnifarious
2个回答

2
IsDerivedFrom2 改为 IsDerivedFrom,编译失败并出现了预期的错误。
问题在于,如果不调用模板类中的方法,则其永远不会被实例化。更改名称使其成为构造函数,因此它最终会被派生自 IsDerivedFrom 的类的构造函数调用。它仍将编译为空代码。编译器将优化掉死代码赋值语句。
如果您可以使用 Boost,特别是 Boost 类型特征库中的 is_base_of,我建议您不要自己编写这样的模板代码。
特别地,可以使用 Boost 更简单、更容易地实现您的 GenericContainer 模板:
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_base_of.hpp>

template <class T>
class GenericContainer
{
private:
    typedef std::vector<T> TypeVect;
    void addElement(const T& elem);

    TypeVect m_elems;

public:
    unsigned int size() const;
    T& elementAt(const unsigned int pos);
    const T& elementAt(const unsigned int pos) const;

    GenericContainer() {
       BOOST_STATIC_ASSERT( (::boost::is_base_of<ContainerItem, T>::value) );
    }
};


template <class T>
void GenericContainer<T>::addElement(const T& elem)
{
    m_elems.push_back(elem);
}

template <class T>
unsigned int GenericContainer<T>::size() const
{
    return m_elems.size();
}

template <class T>
T& GenericContainer<T>::elementAt(const unsigned int pos)
{
    unsigned int maxpos = m_elems.size();
    if (pos < maxpos)
        return m_elems[pos];
    return T::getInvalid();
}


template <class T>
const T& GenericContainer<T>::elementAt(const unsigned int pos) const
{
    unsigned int maxpos = m_elems.size();
    if (pos < maxpos)
        return m_elems[pos];
    return T::getInvalid();
}

感谢您指出我在贴出的代码中没有调用DerivedFrom2的事实。我按照您的建议进行了更改,现在它可以编译了。然而,仍然存在意外的行为。我故意未在派生类Data中实现ABC ContainerItem的纯虚拟成员,但代码成功编译 - 我以为编译器会在编译错误时捕获此错误? - skyeagle
@skyeagle - 当您尝试实例化“Data”的一个实例时,它只会发出错误信号。 - Omnifarious
@skyeagle - 如果不是这样,那么编译器将会为任何具有纯虚方法的类给出错误。所有具有纯虚方法的意思是你不能实例化该类型的对象。 - Omnifarious

1

由于从未引用IsDerivedFrom2,因此未生成Constraints函数。这是C++所需的行为。也许从构造函数中调用它会有所帮助。否则,请检查boost库是否具有类似的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接