C ++模板:编码错误或编译器错误?

时间:2009-05-27 11:15:50

标签: c++ templates stl c++builder

我正在尝试使用模板获取std:项目列表,其中每个项目都有一个指向包含它的列表的指针,但我一直在点击编译器消息。

这是一个非常精简的代码版本。

template <class E> class Item  
{
    public:
        E* owner; // pointer to list that owns us.
};

template <class E> class BaseList: public std::list<E>
{
protected:
    typedef std::list<E> inherited;
public:

    void push_back(const E &e)
    {
        E tmp(e);
        tmp.owner = this;  // This line gives the error.
        inherited::push_back(tmp);
    }
};

class MyList;

class MyItem : public Item<MyList>
{
};

class MyList : public BaseList<MyItem>
{
};

void foo()  // test code to instantiate template
{
    MyList l;
    MyItem m;
    l.push_back(m);
}

然而,我的编译器barf在行: -

        tmp.owner = this;  

错误是:

[BCC32 Error] Unit7.cpp(30): E2034 Cannot convert 'BaseList<MyItem> * const' to 'MyList *'

就像“这个”在某种程度上变成了常量,但我看不出原因。编译器是Codegear C ++ Builder 2009。

我承认我不是100%使用模板,所以我不确定这是我的问题还是编译器。没有模板使用的相同代码编译得很好,但显然这不是我想要的,因为我有几个项目/列表类想要以这种方式工作。

另外,是否有更好的技术可以避免在每个项目中包含所有“所有者”指针?

编辑:我认为我将示例剥离得太过分了:“MyList”实际上引入了新方法,“MyItem”必须通过“owner”指针访问。

摘要:感谢所有评论和回答。正如公认的答案所说,问题只是指向BaseList与MyList之间的类型不兼容之一。

从STL容器和替代设计中提出的问题也很有帮助,但我使用的解决方案基本上与下面的Luc Touraille相同。

8 个答案:

答案 0 :(得分:8)

在第30行,“this”是指向BaseList<MyIteM>的指针,而不是MyList。您可以使用派生类替换类,但不能用其他方式替换。

您可以将myde MyList设置为BaseList<MyItem>,如下所示:

typedef BaseList<MyItem> MyList

或者让MyItem来自Item<BaseList<MyItem> >

从类型派生时,您可以创建不同的类型。当您键入def时,您将为该类型创建别名。因此,当你输入typedef时,编译器会接受这个。

答案 1 :(得分:4)

除了您已有的答案之外,我还要指出标准库集合类并非旨在派生,因为它们没有虚拟析构函数,并且它们的成员函数都不是虚拟的。

答案 2 :(得分:3)

不应该是tmp.owner = static_cast<MyList*>(this)。 E的类型是MyItem中的MyList,因此E *将是MyList *。此指针的类型将是BaseList *,因此编译器会给出错误,即您无法将基类指针转换为派生类指针。

答案 3 :(得分:2)

当你没有说出需要时,很难说是否有更好的解决方案。 为什么每个元素都需要指向它们存储在列表中的指针?

无论如何,当您从标准容器继承时,可能会发生不好的事情。他们没有虚拟析构函数,所以你必须非常小心。

更好的解决方案可能是提供执行push_back的免费功能:

template <typename T>
void push_back(std::list<T>& list, const T& t) {
        T tmp(t);
        tmp.owner = this;
        list.push_back(tmp);
}

除了避免非虚拟析构函数问题之外,它还解决了编译器错误,因为您现在只有一种类型的列表。

当然,如果我们首先知道你为什么需要这个所有者指针,那么可能存在更好的解决方案。

修改

在回复您的编辑和评论时,请使用合成,而不是继承:

struct House {
  std::string zipcode;
  std::list<Person> persons;

  void AddPerson(const Person& person) {
    Person tmp(person);
    tmp.owner = this; // The owner field should be a house, not the list of persons.
    persons.push_back(tmp);
  }
};

虽然我没有卖出几乎循环的引用,但是当一个House存储一个人员列表时,你会得到一个人,并且一个人有一个指向它所在的House的指针。

我更愿意尽可能地分离这些类。如果我想给某人发信,我会打电话给SendLetter(人,房)。在那所房子里给这个人发一封信。

答案 4 :(得分:1)

在旁注中,你不应该从std扩展任何类,它们不是为它构建的。

具体来说,它们没有虚析构函数,因此当您在指向基类的指针上调用delete时,派生类的析构函数将永远不会被调用。

您可以阅读更多内容Advice on a better way to extend C++ STL container with user-defined methods

答案 5 :(得分:1)

我喜欢jalf的自由功能理念。我做到了:

template <class X, class Y> // X must have a push_back(Y) member, Y must have an X* owner member
void push_back(X& container, Y value)
{
    value.owner = container;
    container.push_back(value);
}

这与X传递的是否

无关
  • 一个容器本身,
  • 是从原始代码中的容器派生的
  • 或包含容器并具有转发push_back功能

答案 6 :(得分:1)

正如已经指出的那样,影响

tmp.owner = this;

失败,因为thistmp.owner的类型不同。一种解决方案是执行强制转换,但为此,您需要将容器类型提供给BaseList。这可以使用Item中的typedef来完成。这是代码:

template <class Item> class BaseList
{      
  public:

    void push_back(Item i)
    {
        i.owner = static_cast<Item::containerType *>(this); // note the cast
        items.push_back(i);
    }

    Item & back() { return items.back(); }

  protected:
    std::list<Item> items;
};


template <class Container> class Item
{
    public:
        typedef Container containerType; // Typedef used by BaseList

        containerType* owner; // pointer to list that owns us. 
};

我还删除了std::list的公开派生:正如许多人所说,这是(大部分时间)最好避免的;你应该考虑使用组合,或者私人继承。

P.S。:我尝试让owner私人和BaseList<Item>::push_back朋友成为Item,但我没有成功。有可能吗? (如果在评论中回答的时间太长,请随时提出问题并回答)

答案 7 :(得分:1)

关于const编译器提到的类型BaseList<MyItem> * const是一个红色的鲱鱼 - 它不是指向一个指针 - const - 对象,但是 const的指针,即一个不会改变的地址。 (当你想到它时,this永远不会改变指向别的东西,是吗?)