1

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

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

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);
}

但是,我的编译器在该行中遇到了问题:-

        tmp.owner = this;  

错误是:

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

就像“this”不知何故变成了const,但我不明白为什么。编译器是 Codegear C++Builder 2009。

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

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

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

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

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

4

8 回答 8

8

在第 30 行,“this”是指向 a 的指针BaseList<MyIteM>,而不是 MyList。您可以用派生类替换一个类,但反过来不行。

您可以 typedef MyList 为 a BaseList<MyItem>,如下所示:

typedef BaseList<MyItem> MyList

或者让 MyItem 派生自Item<BaseList<MyItem> >

当您从一个类型派生时,您将创建一个不同的类型。当您 typedef 时,您为该类型创建了一个别名。所以当你 typedef 编译器会接受这个。

于 2009-05-27T11:24:43.273 回答
4

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

于 2009-05-27T11:30:10.877 回答
3

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

于 2009-05-27T11:28:08.057 回答
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 存储 Person 列表时获得的几乎循环引用,并且 Person 有一个指向它所在的 House 的指针。

我更愿意尽可能地解耦这些类。如果我想给某人寄信,我会调用 SendLetter(Person, House)。给那个房子里的这个人写一封信。

于 2009-05-27T11:37:57.273 回答
1

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

于 2009-05-27T19:47:06.380 回答
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 功能
于 2009-05-27T12:05:14.380 回答
1

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

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

您可以阅读更多关于使用用户定义方法扩展 C++ STL 容器的更好方法的建议

于 2009-05-27T11:39:25.087 回答
1

正如已经指出的,做作

tmp.owner = this;

失败,因为this没有与 . 相同的类型tmp.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:正如许多人所说,这是(大多数情况下)最好避免的;您应该考虑使用组合,或者可能是私有继承。

PS:我尝试将Item设为owner私人和BaseList<Item>::push_back朋友,但我没能做到。有可能吗?(如果评论太长无法回答,请随时提出问题并回答)

于 2009-05-27T16:46:21.363 回答