在C++中将Map内的派生类传递给函数[duplicate]

tvmytwxo  于 2023-01-15  发布在  其他
关注(0)|答案(3)|浏览(137)
    • 此问题在此处已有答案**:

Inheritance in std::map with base class as value(4个答案)
3天前关闭。
我希望有一个函数能接受一个std::map,它包含一个基类(指向一个基类的指针),但是我不能让它工作,下面是我的最小化和简化的例子:

struct Entity {
  int i;

  Entity(const int i_) : i(i_) {}
  virtual ~Entity() {}
};

struct A : public Entity {
  bool a;
  A(const int i_, const bool a_) : Entity(i_), a(a_) {}
};

void efunc(const std::map<int, std::shared_ptr<Entity>>& m) {
  for (auto& it : m) {
    std::cout << it.second->i << std::endl;
  }
}

int main() {
  std::map<int, std::shared_ptr<A>> aMap;
  std::shared_ptr<A> myA = std::make_shared<A>(1, true);
  aMap.insert({myA->i, myA});

  // efunc(aMap);  // DOES NOT WORK
}

显然,简单地以这种方式传递map是不允许的,但是我如何才能使它工作呢?
另外,如何在Map中检测类型,以便执行特定于该子类的代码?
先谢谢你!

    • 更新:**使用模板似乎很有效,这就是我现在使用的模板:
template <class T>
void tfunc(const std::map<int, std::shared_ptr<T>>& m) {
  for (auto& it : m) {
    std::cout << it.second->i << std::endl;
  }
}

这确实感觉有点奇怪,因为我失去了智能感知,我不明白为什么编译器没有给我一个错误。我觉得concept s可能会有帮助(例如,确保T继承Entity),但这超出了我现在需要的。
再次感谢您的所有回应!

    • 更新2:**好的,这里使用概念来确保template包含成员i
template <class T>
concept hasI = requires(T a) { a.i; };

template <class T>
concept inheritsEntity = std::derived_from<T, Entity>;

template <hasI T>  
/// alternative: template<inheritsEntity T>
void tfunc(const std::map<int, std::shared_ptr<T>>& m) {
  for (auto& it : m) {
    std::cout << it.second->i << std::endl;
  }
}
9cbw7uwe

9cbw7uwe1#

要回答第一个问题,可以使用 * 模板编程 *;编译器将为您生成特定于所需类型的代码:

template <class EntityType> 
void efunc_template(const std::map<int, std::shared_ptr<EntityType>>& m) {
  for (auto& it : m) {
    std::cout << it.second->i << std::endl;
  }
}

然后可以在main函数中使用以下命令调用该函数:

efunc_template<A>(aMap);

关于第二个问题(如何执行特定于您的类型的代码),可以在编译时使用以下命令进行检查:

template <class EntityType> 
void efunc_template(const std::map<int, std::shared_ptr<EntityType>>& m) {
  for (auto& it : m) {
    if constexpr (std::is_same_v<A, EntityType>) {
      std::cout << "Type is A" << std::endl;
    } else {
      std::cout << "Other type: " << typeid(EntityType).name() << std::endl;
    }
    std::cout << it.second->i << std::endl;
  }
}
gab6jxml

gab6jxml2#

您可以将aMap设置为std::map<int, std::shared_ptr<Entity>>,但仍然保留指向A示例的共享指针。
在下面的代码中,将存储在Map中的myA的类型为std::shared_ptr<Entity>,但初始化时使用了指向类A示例的共享指针。这是可以的,因为A是从Entity派生的。

//----------------------------VVVVVV--------
std::map<int, std::shared_ptr<Entity>> aMap;

//--------------VVVVVV-------------------------V-----------
std::shared_ptr<Entity> myA = std::make_shared<A>(1, true);

aMap.insert({ myA->i, myA });

efunc(aMap);  // Works now.

如下面所述,myA也可以是std::shared_ptr<A>,并且仍然存储在aMap中:

std::shared_ptr<A> myA = std::make_shared<A>(1, true);

或者,如果很少使用efunc,则可以将aMap保留为std::map<int, std::shared_ptr<A>>
然后,每当需要调用efunc时,创建一个包含aMap内容副本的临时std::map<int, std::shared_ptr<Entity>>,并使用它调用efunc
共享指针仍将引用原始对象。
它的效率很低,但如果您不经常使用efunc,它可能就足够了。

mklgxw1f

mklgxw1f3#

将std::map变量的声明从:

std::map<int, std::shared_ptr<A>> aMap;

致:

std::map<int, std::shared_ptr<Entity>> aMap;

这样,Map就与efunc函数所期望的相匹配。
使用继承,你确实可以在aMap数据结构中插入Entity类型的示例,以及从Entity派生类型的示例,struct A就是这种情况。
要检测map中的类型,可以使用一个作用域枚举作为Entity结构中的成员,也可以使用typeid运算符,该运算符返回std::type_info的示例,通过该示例可以获取类型的名称。
但是,不保证返回的名称在不同的实现中相同。
========从叮当声输出========
myint具有类型:我
mystr具有类型:NSt3__112基本字符串IcNS_11字符特性IcEENS_9分配器IcEEEE
======== MSVC的输出========
myint具有类型:整数
mystr具有类型:类标准::基本字符串〈字符,结构〉标准::字符特性,类标准::分配器〉
因此,您可能希望改用dynamic_cast,例如:

class Base {
    public:
        virtual ~Base() {}
};

class T1Base : public Base {};
class T2Base : public Base {};

template <typename T> class T1 : public T1Base {};
template <typename T> class T2 : public T2Base {};

int main()
{
    Base *b = find_by_name();

    if (dynamic_cast<T1Base*>(b))
        cout << "T1" << endl;
    else if (dynamic_cast<T2Base*>(b))
        cout << "T2" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}

相关问题