Шаблоны C ++: ошибка кодирования или ошибка компилятора? - PullRequest
1 голос
/ 27 мая 2009

Я пытаюсь использовать шаблоны для получения 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);
}

Тем не менее, мой компилятор barfs в строке: -

        tmp.owner = this;  

Ошибка:

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

Как будто "это" каким-то образом стало постоянным, но я не понимаю, почему. Компилятор - Codegear C ++ Builder 2009.

Я признаю, что не на 100% доволен использованием шаблонов, поэтому я не уверен, является ли это моей проблемой или компиляторами. Тот же код без использования шаблона прекрасно компилируется, но, очевидно, это не то, что я хочу, так как у меня есть несколько классов item / list, которые хотят работать таким образом.

Кроме того, есть ли лучшая техника, позволяющая избежать указания всех указателей "владельца" на каждый элемент?

РЕДАКТИРОВАТЬ: Я думаю, я сократил пример слишком далеко: «MyList» фактически вводит новые методы, к которым «MyItem» должен затем обращаться через указатель «владельца».

РЕЗЮМЕ: Спасибо за все комментарии и ответы. Как говорится в принятом ответе, проблема заключается просто в несовместимости типов между указателем на BaseList и MyList.

Вопросы, связанные с производством контейнеров STL и альтернативных конструкций, также полезны, но решение, которое я использовал, по сути идентично приведенному ниже Люку Турэ.

Ответы [ 8 ]

8 голосов
/ 27 мая 2009

В строке 30 «this» - это указатель на BaseList<MyIteM>, а не MyList. Вы можете заменить класс производным, но не наоборот.

Вы можете ввести MyList либо как BaseList<MyItem>, например:

typedef BaseList<MyItem> MyList

или позвольте MyItem получить из Item<BaseList<MyItem> >.

Когда вы наследуете тип, вы создаете другой тип. Когда вы печатаете def, вы создаете псевдоним для этого типа. Поэтому, когда вы вводите определение, компилятор примет это.

4 голосов
/ 27 мая 2009

В дополнение к ответам, которые у вас уже есть, я бы также отметил, что стандартные классы библиотечных коллекций не предназначены для получения, поскольку в них нет виртуальных деструкторов, и ни одна из их функций-членов не является виртуальной.

3 голосов
/ 27 мая 2009

Не должно ли быть tmp.owner = static_cast<MyList*>(this). Тип E - MyList в MyItem, следовательно, E * будет MyList *. Тип этого указателя будет BaseList *, поэтому компилятор выдает ошибку, что вы не можете преобразовать указатель базового класса в указатель производного класса.

2 голосов
/ 27 мая 2009

Трудно сказать, есть ли лучшее решение, когда вы не говорите, что именно вам нужно . Почему каждому элементу нужен указатель на список, в котором они хранятся?

В любом случае, плохие вещи могут случиться, если вы наследуете от стандартных контейнеров. У них нет виртуальных деструкторов, поэтому вы должны быть очень осторожны.

Лучшим решением может быть предоставление бесплатной функции, выполняющей 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);
  }
};

Хотя я не продаюсь по почти круговым ссылкам, которые вы получаете, когда Дом хранит список людей, а у Человека есть указатель на Дом, в котором он находится.

Я бы предпочел разделить эти классы как можно больше. Если я хочу отправить письмо человеку, я бы позвонил SendLetter (Person, House). Отправь письмо этому человеку в этом доме.

1 голос
/ 27 мая 2009

Относительно const: Тип BaseList<MyItem> * const, который компилятор упоминает, является красной сельдью - это не указатель на const -объект, а указатель, который is const, то есть адрес, который не изменится. (Когда вы думаете об этом, this никогда не меняется, чтобы указывать на что-то еще, не так ли?)

1 голос
/ 27 мая 2009

Как уже указывалось, аффект

tmp.owner = this;

терпит неудачу, потому что this не имеет тот же тип, что и tmp.owner. Одним из решений является выполнение приведения, но для этого вам необходимо предоставить тип контейнера для BaseList. Это можно сделать с помощью typedef в Item. Вот код:

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, но мне не удалось это сделать. Это вообще возможно? (Если слишком долго отвечать в комментарии, не стесняйтесь задавать вопрос и отвечать на него)

1 голос
/ 27 мая 2009

Мне нравится идея свободной функции 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
1 голос
/ 27 мая 2009

Обратите внимание: вы не должны расширять классы из std, они не созданы для него.

В частности, у них нет виртуального деструктора, поэтому, когда вы вызываете delete по указателю на базовый класс, деструктор вашего производного класса никогда не будет вызван.

Подробнее об этом можно прочитать Советы по улучшению способа расширения контейнера C ++ STL с помощью пользовательских методов

...