C++ наследование и ковариация контейнеров

Я пытаюсь построить следующую архитектуру:

struct A{
   int x;
}
struct B: public A{
   int additinal_data;
}
struct ContainerA{
  std::vector<A> va; 
}
struct ContianerB{
  std::vector<B> vb;
  int additional_data;
}

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

double sum(ContainerA &ca) {
   double res = 0;
   for (const auto &a: ca.va) 
       res += a.x;
   return res;
}

Также было бы неплохо иметь возможность перемещать симуляционную функцию в качестве члена ContainerA, но быть доступной также и из ContainerB (т.е. ContainerA ca; ca.sum()). это так. Как я могу переделать свою архитектуру, чтобы разрешить такой вызов?


person Solon    schedule 18.08.2020    source источник
comment
Почему бы не добавить перегрузку для ContianerB?   -  person NathanOliver    schedule 18.08.2020
comment
Как насчет struct ContianerB : public ContainerA?   -  person goodvibration    schedule 18.08.2020
comment
Поскольку я не знаю других требований к вашим классам, я бы хотел создать родительский класс для ContainerA и ContainerB и использовать его в качестве полиморфного контейнера.   -  person Miguel    schedule 18.08.2020
comment
@goodvibration Я не знаю, как это сделать без дублирования данных. Поскольку мне нужно сохранить вектор «A» в ContrainerA и вектор «B» в ContainerB, где B расширяет A   -  person Solon    schedule 18.08.2020
comment
См. ответ Стива Джессопа на этот вопрос, который использует виртуальный getIdentifier   -  person    schedule 18.08.2020
comment
@BlayerBond: вы, вероятно, хотите сказать этот ответ, поскольку представитель изменится (а ваш комментарий не изменится).   -  person goodvibration    schedule 18.08.2020
comment
@goodvibration (Не по теме): Да я уже заменил на автора. Как получить ссылку на ответ?   -  person    schedule 18.08.2020
comment
@BlayerBond под ответом поделиться   -  person 463035818_is_not_a_number    schedule 18.08.2020
comment
@BlayerBond: используя текст share внизу ответа.   -  person goodvibration    schedule 18.08.2020
comment
@BlayerBond Спасибо, я прочитал это, но не смог связать это со своим вопросом. Мой вопрос не о ковариантных возвращаемых типах, а о том, как выразить, что ContainerB расширяет ContainerA   -  person Solon    schedule 18.08.2020
comment
Я также предпочел бы не иметь здесь дело с полиморфизмом времени выполнения, так как он выглядит излишним.   -  person Solon    schedule 18.08.2020
comment
@Solon, я пытался написать рабочий пример с типами возврата, отличными от const&, но мне это не удалось, поэтому я решил не добавлять ответ. (Я привык иметь virtual CoParent* Identifier().) Почему это связано? В случае, если ContainterB ведет себя как ContainterA или struct ContianerB : public ContainerA, вы можете предоставить ContainerA метод virtual A* getElement(const int& index); и предоставить ContainerB метод B* getElement(const int& index) override;. Таким образом, вам не нужен член std::vector<B> в ContainerB, потому что вы знаете, что элементы va будут иметь тип B.   -  person    schedule 18.08.2020


Ответы (4)


Один из способов использования sum с ContainerA или ContainerB.

  1. Сделайте функцию шаблоном функции.
  2. Обновите ContainerA и ContainerB, чтобы их можно было использовать в цикле range-for и многих функциях из заголовка <algorithm> стандартной библиотеки.
struct ContainerA
{
  std::vector<A> va; 

  // Add const and non-const versions of begin() and end().

  using iterator = std::vector<A>::iterator;
  using const_iterator = std::vector<A>::const_iterator;

  iterator begin() { return va.begin(); }
  iterator end() { return va.end(); }

  const_iterator begin() const { return va.begin(); }
  const_iterator end() const { return va.end(); }

};

struct ContianerB
{
  std::vector<B> vb;
  int additional_data;

  // Add const and non-const versions of begin() and end().

  using iterator = std::vector<B>::iterator;
  using const_iterator = std::vector<B>::const_iterator;

  iterator begin() { return vb.begin(); }
  iterator end() { return vb.end(); }

  const_iterator begin() const { return vb.begin(); }
  const_iterator end() const { return vb.end(); }

};

template <typename Container>
double sum(Container const& container)
{
   double res = 0;
   for (const auto &a: container) 
       res += a.x;
   return res;
}
person R Sahu    schedule 18.08.2020
comment
Спасибо, я думал об этом. Единственная проблема, с которой я столкнулся, заключается в том, что здесь отсутствует явное отношение того, что ContainerB расширяет ContainerA, поэтому я не могу переместить эту сумму в ContainerA. Думаете, эту проблему тоже можно решить? - person Solon; 18.08.2020
comment
@Солон, это искусственное ограничение, которое ты налагаешь на себя. Предпочитайте использовать функции, не являющиеся членами, когда это возможно. - person R Sahu; 18.08.2020

как выразить, что ContainerB расширяет ContainerA

На данный момент нет. Однако вы можете легко изменить это:

struct ContainerA{
  std::vector<A> va; 
};
struct ContianerB{
  std::vector<B> va;
  int additional_data;
};

Статический полиморфизм работает лучше всего, когда имена совпадают.

А потом

template <typename Container>
double sum(Container const& container) 
/* requires AVector<Container> */
{
   double res = 0;
   for (const auto &a: container.va) 
       res += a.x;
   return res;
}

С концепциями C++20 для лучшей проверки

template<typename T> concept ARange = 
    std::range<T>
 && std::derived_from<T::value_type, A>;
person Caleth    schedule 18.08.2020
comment
Спасибо за ваш ответ. Что вы думаете о сильных в ContainerB только аксиальных данных (вектор дополнительных_данных). Таким образом, я могу явно расширить ContainerA с помощью ContainerB struct ContianerB : public ContainerA{ std::vector<int> additional_data; int additional_data; }; - person Solon; 19.08.2020
comment
@Solon My ContianerB можно заменить на (только для чтения) ContainerA, не наследуя его, но при этом сохраняя добросовестные Bs - person Caleth; 19.08.2020
comment
Я хотел бы сохранить ссылку на ContainerA (или ContainerB) в другом потоке, чтобы наблюдать за изменениями (в ContainerA.va) - person Solon; 20.08.2020

Я думаю, что невозможно static_cast связать Derived ссылку значения на ссылку его Base типа в методе getIdentifer, как неявно показано в пример ковариации Стива Джессопа, потому что тогда вы будете создавать временный ссылка, но могу ошибаться. Поэтому вместо этого я сделал метод (названный getElement), который возвращает возможно приведенный указатель, который обязательно будет существовать.

Поскольку вам может понадобиться явная связь между ContainerA и ContainerB (в случае дублирования кода), я написал пример на основе class того, как вы могли бы это реализовать. В противном случае, использование шаблонной функции, вероятно, является выходом.

Если вы используете структуры, вам не нужны ни метки public, protected и private, ни сеттеры для переменных, но вам, конечно, нужен некоторый метод getIdentifier для доступа к элементам типа B.

Вот демонстрационная программа:

A

#pragma once
#include <iostream>

class A
{
public:
    A(const int& data);

    friend std::ostream& operator<<(std::ostream& s, const A& a);
protected:
    virtual std::ostream& toStream(std::ostream& s) const;
private:
    int m_data;
};

A::A(const int& data)
    : m_data{ data }
{
}

std::ostream& A::toStream(std::ostream& s) const
{
    return s << m_data;
}

std::ostream& operator<<(std::ostream& s, const A& a)
{
    return a.toStream(s);
}

B

#pragma once
#include "A.h"

class B : public A
{
public:
    B(const int& data, const int& additionalData);
    void setAdditionalData(int additionalData);
protected:
    virtual std::ostream& toStream(std::ostream& s) const;
private:
    int m_additinalData;
};

B::B(const int& data, const int& additionalData)
    : A{ data },
    m_additinalData{ additionalData }
{
}

void B::setAdditionalData(int additionalData)
{
    m_additinalData = additionalData;
}

std::ostream& B::toStream(std::ostream& s) const
{
    A::toStream(s);
    return s << '\t' << m_additinalData;
}

Контейнер А

#pragma once
#include "A.h"

#include <vector>

class ContainerA
{
public:
    void push(A* a);
    size_t getSize() const;
    virtual A* getElement(const int& index);
    virtual A* operator[](const int& index);
protected:
    std::vector<A*> m_As;
};

void ContainerA::push(A* a)
{
    m_As.push_back(a);
}

A* ContainerA::getElement(const int& index)
{
    return m_As[index];
}

A* ContainerA::operator[](const int& index)
{
    return m_As[index];
}

size_t ContainerA::getSize() const
{
    return m_As.size();
}

КонтейнерБ

#pragma once
#include "ContainerA.h"
#include "B.h" // The compiler should be able to tell that B is a subclass of A

class ContainerB : public ContainerA
{
public:
    B* getElement(const int& index) override;
    B* operator[](const int& index) override;
private:
    int additional_data;
};

B* ContainerB::getElement(const int& index)
{
    return static_cast<B*>(m_As[index]);
}

B* ContainerB::operator[](const int& index)
{
    return static_cast<B*>(m_As[index]);
}

main.cpp

#include "ContainerB.h"

int main()
{
    B b1{ 4, -1 };
    B b2{ 5, -1 };
    B b3{ 6, -1 };

    ContainerB contB{};
    contB.push(&b1);
    contB.push(&b2);
    contB.push(&b3);

    B* b{ contB.getElement(0) };
    b->setAdditionalData(0);

    size_t size{ contB.getSize() };
    for (int i{ 0 }; i < size; ++i) {
        std::cout << *contB.getElement(i) << std::endl;
        std::cout << *contB[i] << std::endl;
    }
}

Вывод

4       0
4       0
5       -1
5       -1
6       -1
6       -1

Теперь вы можете передать ContainerB функции/методу, который ожидает ContainerA, без необходимости хранить избыточные данные.

person Community    schedule 18.08.2020
comment
спасибо за отличный ответ. Одна вещь, которую я не сказал, это то, что статический полиморфизм во время выполнения слишком дорог для меня в этом примере. (в основном потому, что эти функции будут вызываться миллионы раз и в вашем случае не могут быть встроены) - person Solon; 19.08.2020

Рассмотрите возможность использования статического полиморфизма (шаблоны).

(код не тестировался)

auto const& inner_vector(ContainerA& ca){return ca.va;}
auto const& inner_vector(ContainerB& cb){return cb.vb;}

template<class TContainer>
double sum(TContainer &c){
   double res = 0;
   for (const auto &a: inner_vector(c)) 
       res += a.x;
   return res;
}
person alfC    schedule 18.08.2020