Предоставление итераторов c для устаревшего интерфейса связанного списка C

#c #iterator #wrapper

#c #итератор #оболочка

Вопрос:

У меня есть приложение Visual Studo 2008 C , в котором я пытаюсь добавить поддержку итераторов в структуру связанного списка устаревшего C-API. Интерфейс C выглядит следующим образом:

 typedef struct _LINKED_LIST_INFO {
    struct _LINKED_LIST_INFO* Next;
    const char* name;
    // more elements. some are fixed-size; others are pointers to other structures.
} LINKED_LIST_INFO;

DWORD GetLinkedList( LINKED_LIST_INFO* pInfo, PULONG pOutBufLen );
  

Я хотел бы иметь возможность использовать его следующим образом:

 int _tmain( int /*argc*/, _TCHAR* /*argv*/[] )
{
    MyLinkedList elements;
    for( MyLinkedList::const_iterator it = elements.begin();
         it != elements.end();
           it )
    {
        printf( "Name: %srn", it->Name().c_str() );
    }

    return 0;
}
  

Итак, я создал эти 3 класса. Но у моего MyInfoIterator класса есть проблема с operator->() . Я не могу вернуть временный указатель на MyInfo , поэтому я получаю сообщение об ошибке: error C2440: 'return' : cannot convert from 'MyInfo' to 'const MyInfo *'

Каково хорошее решение этой проблемы?

 /// wrap the legacy C structure and provide C   accessors
class MyInfo
{
public:

    MyInfo( const LINKED_LIST_INFOamp; info ) : elem_( info ) { };

    std::string Name() const { return elem_.name; };

private:
    /// one element of the linked list
    const LINKED_LIST_INFOamp; elem_;
}; // class MyInfo

namespace detail {

/// buffer to hold the legacy C linked-list
typedef std::vector< BYTE > MyBuffer;

/// iterator support for the legacy C linked-list
class MyInfoIterator 
    : public std::iterator< std::input_iterator_tag, MyInfo > 
{
public:
    explicit MyInfoIterator( MyBufferamp; list ) : data_( list )
    {
        elem_ = reinterpret_cast< LINKED_LIST_INFO* >( amp;data_.front() );
    };

    MyInfoIterator() : elem_( NULL ) { };

    MyInfoIteratoramp; operator  () 
    {
        elem_ = elem_->Next;
        return *this;
    };

    value_type operator*() { return *elem_; };

    //  error C2440: 'return' : cannot convert from 'MyInfo' to 'const MyInfo *'
    const value_type* operator->() { return elem_; };

    friend bool operator==( const MyInfoIteratoramp; i, 
                            const MyInfoIteratoramp; j ) 
    { 
        return i.elem_ == j.elem_;
    };

private:

    /// linked-list of elements
    MyBuffer data_;

    /// current position within the element list
    LINKED_LIST_INFO* elem_;

}; // class MyInfoIterator

bool operator!=( const MyInfoIteratoramp; i, const MyInfoIteratoramp; j ) 
{ 
    return !operator==( i, j );
}

}; // namespace detail

/// provide iterator access for the legacy C linked-list API
class MyLinkedList
{
public:
    typedef detail::MyInfoIterator const_iterator;

    const_iterator begin() const 
    { 
        ULONG size = sizeof( LINKED_LIST_INFO );
        detail::MyBuffer buffer( size );

        DWORD ec = ::GetLinkedList( 
            reinterpret_cast< LINKED_LIST_INFO* >( amp;buffer.front() ), amp;size );
        if( ERROR_BUFFER_OVERFLOW == ec )
        {
            buffer.resize( size );
            ec = ::GetLinkedList( 
                reinterpret_cast< LINKED_LIST_INFO* >( amp;buffer.front() ), amp;size );
        }

        if( ERROR_SUCCESS != ec )
            Win32Exception::Throw( ec );

        return const_iterator( buffer ); 
    };

    const_iterator end() const { return const_iterator(); };

}; // class MyInfo
  

Спасибо,
PaulH

Редактировать:

Я не могу изменить устаревший API или связанную с ним структуру.

Правка2:

Я думаю, что у меня есть рабочее решение, которое сохраняет мое намерение скрыть базовую реализацию связанного списка и поддерживает разделение обязанностей для каждого класса, возвращая адрес static MyInfo .

 class MyInfo
{
   // ...
protected:
    MyInfo() : info_( NULL ) { };
    void Set( const LINKED_LIST_INFO* info ) { info_ = info; };
private:
    friend MyInfoIterator;
    const LINKED_LIST_INFO* info_;
};

const value_typeamp; MyInfoIterator::operator*() const 
{ 
    static MyInfo info;
    info.Set( elem_ );
    return info;
};

const value_type* MyInfoIterator::operator->() const
{ 
    static MyInfo info;
    info.Set( elem_ );
    return amp;info; 
};
  

Комментарии:

1. return amp;elem_; … удачи и держите под рукой нож для колки льда на всякий случай.

2. не обращайте внимания на болезненное чувство юмора: P

Ответ №1:

Упрощайте :

 class MyInfoIterator 
    : public std::iterator< std::input_iterator_tag, _LINKED_LIST_INFO >
{
   _LINKED_LIST_INFO* p;
public;
    MyInfoIterator(_LINKED_LIST_INFO* pointer) : p(pointer) {}

    [Same as you did]

    value_typeamp; operator*() { return *p; }
}
  

Комментарии:

1. Я не хочу предоставлять следующий указатель в связанном списке. Итак, я возвращаю класс доступа ( MyInfo ), который скрывает базовую реализацию LL.

2. 1: Это иллюстрирует проблему с типом значения, о которой я упоминал, и допустимо, если вам не нужен вспомогательный класс для доступа, если вы заменяете MyInfo вместо LINKED_LIST_INFO везде.

Ответ №2:

Я считаю, что ваш value_type ошибочен… если вы наследуете итератор STL в качестве интерфейса, тип значения должен соответствовать типу, который вы содержите. В вашем случае вы говорите, что вы содержите элементы MyInfo , но вы пытаетесь вернуть LINKED_LIST_INFO*

Либо верните MyInfo , либо объявите, что в вашем контейнере итератора есть value_type of LINKED_LIST_INFO .

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

Редактировать:

Естественно, вы должны понимать, что простое наличие std::vector<LINKED_LIST_INFO> or std::vector<MyInfo> дает вам всю необходимую функциональность без каких-либо проблем с реализацией и обслуживанием.

Правка # 2:

На самом деле у вас не может быть a std::vector<MyInfo> потому что он не может быть сконструирован по умолчанию, поэтому, как только вы разрешите свою текущую ошибку, вы получите другую, основанную на typedef std::vector< MyInfo > MyBuffer; , которая не будет разрешена при построении шаблона, пока вы не предоставите конструктор по умолчанию для MyInfo

Правка # 3:

Ваш value_type operator*() { return *elem_; } довольно неправильно сформирован. Вы не должны возвращать копию внутреннего объекта после этой двоичной операции. Вы должны вернуть ссылку. В вашем случае вы рассматриваете это как deref, а не как операцию умножения, что нормально, но возврат копирования по значению по-прежнему неверен.

Комментарии:

1. Я не хочу предоставлять следующий указатель в связанном списке. Итак, я возвращаю класс доступа ( MyInfo ), который скрывает базовую реализацию LL.

2. Я не могу использовать ни std::vector<MyInfo> , ни std::vector<LINKED_LIST_INFO> . Базовая структура связанного списка имеет различный размер (а не кратный sizeof LINKED_LIST_INFO ), поэтому вектор должен представлять собой массив байтов. Это то, что я пытался передать с помощью строки комментария в LINKED_LIST_INFO структуре и наличия там const char* name параметра.

3.const char * всегда имеет одинаковый размер … это указатель на постоянные данные char. Даже в случае символа * и динамического выделения внутренних элементов размер указателя остается размером указателя. Однако это опасно ненадежный дизайн в современном C , который вам следует опасаться когда-либо видеть delete или delete[] в хорошо разработанном коде ООП. Предпочитаю интеллектуальные указатели. Тем не менее, пункт о указателях остается прежним. sizeof(LINKED_LIST_INFO) вероятно, это не меняется 😉

4. Я не уверен, что понимаю. Здесь не используется ни delete , ни delete[] . sizeof LINKED_LIST_INFO остается прежним, но я не могу использовать a, vector<LINKED_LIST_INFO> потому что LINKED_LIST_INFO это не массив типов, а связанный список. Это: LINKED_LIST_INFO* a=/*...*/; a; было бы неправильно.

5. Это был случай слишком большого объема информации, извините, что сбиваю с толку. Итак, вы говорите, что LINKED_LIST_INFO используются узлы, а память выделяется не последовательно. Достаточно справедливо использовать std::vector<MyInfo> и спроектировать вспомогательный класс MyInfo для обеспечения доступа к данным и перехода от узла к узлу.

Ответ №3:

Это ужасно (вы не должны этого делать) :

 class MyInfo {
   char* name() const {
      return ((_LINKED_LIST_INFO*)this)->name;
   }
};

class MyInfoIterator 
: public std::iterator< std::input_iterator_tag, MyInfo>
{
   _LINKED_LIST_INFO* p;
public:
    MyInfoIterator(LINKED_LIST_INFO* pointer) : p(pointer) {}

    [Same as you did]

    reference operator*() const { return *(MyInfo*)p; }
};
  

Чистый способ, вероятно, :

 class MyInfo {
public:

  [...]

  private:
    const LINKED_LIST_INFO* elem_;
}

class MyInfoIterator 
: public std::iterator< std::input_iterator_tag, MyInfo>
{
   mutable MyInfo p;
public:
    MyInfoIterator(LINKED_LIST_INFO* pointer) : p(pointer) {}

    [...]

    referenceamp; operator*() const { return p; }
    pointer_type operator->() const { return amp;p; }
};
  

Вот как реализованы некоторые итераторы Boost (я имею в виду чистый способ, а не первый), см., например, [1].

[1] http://www.boost.org/doc/libs/1_46_1/libs/iterator/doc/counting_iterator.html

Комментарии:

1. Я согласен, что это выглядит ужасно, но я не понимаю: если это ужасно, почему вы включаете это в свой ответ?

2. Ваш оператор * возвращает value_type вместо ссылки. Вместо этого он должен возвращать ссылку. Это ужасный обходной путь (законно ли это?). Вероятно, чистым способом является реализация итератора как имеющего MyInfo (однако его элемент не должен быть ссылкой).

3. Я думаю, что мое ужасное решение чище 🙂 Вы столкнетесь с проблемами, если будете использовать более одного итератора этого типа в одном и том же типе (подумайте о вложенных циклах, подумайте о нескольких потоках).

4. Я думаю, я собираюсь использовать ваше изменяемое решение. Спасибо!

Ответ №4:

Похоже, что для этого достаточно просто вернуть адрес elem_ .

const value_type* operator->() { return amp;elem_; };

Вы уверены, что не было бы меньше работы, если бы вы изменили свой устаревший список на std::list или std::vector , если это уместно?

Комментарии:

1. Это изменение выдает мне error C2440: 'return' : cannot convert from 'LINKED_LIST_INFO **' to 'const MyInfo *' ошибку. Я не могу изменить устаревший API или связанную с ним структуру.

2. Если вы предлагаете мне сделать это: const value_type* operator->() { return amp;AdapterInfo( elem_ ); }; Затем я возвращаю адрес, не являющийся значением lvalue.