问题描述
我正在尝试实现一个返回 std::unordered_map
、double
或 int
对的 std::string
。地图的键是 std::string
。以下是我迄今为止尝试过的:
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <vector>
// A base class for boundary class
class Boundbase {
public:
Boundbase(){};
virtual ~Boundbase(){};
};
// A different map of boundaries for each different data type
template <class dType>
class Boundary : public Boundbase {
std::pair<dType,dType> bpair;
public:
//Constructor
Boundary(const std::string &lbound,const std::string &ubound) {
setbound(lbound,ubound);
};
//A method to set boundary pair
void setbound(const std::string &lbound,const std::string &ubound);
// A method to get boundary pair
std::pair<dType,dType> getbound() {return bpair;}
};
// Class to hold the different boundaries
class Boundaries {
std::unordered_map<std::string,Boundbase*> bounds;
public:
//Constructor
Boundaries() {};
// A method to set boundary map
void setboundmap(std::unordered_map<std::string,std::vector<std::string>> xtb);
// A template to get boundaries.
std::unordered_map<std::string,Boundbase*> getbounds()
{return bounds;}
};
// A method to set covariate boundary
template <class dType> void
Boundary<dType>::setbound(const std::string &lbound,const std::string &ubound) {
dType val;
std::istringstream isa(lbound);
while(isa >> val) {
bpair.first = val;
}
std::istringstream isb(ubound);
while(isb >> val) {
bpair.second = val;
}
}
// A method to set boundary map
void Boundaries::setboundmap(std::unordered_map<std::string,std::vector<std::string>> xtb) {
for(auto s : xtb) {
char type = s.second[1][0];
switch(type) {
case 'd': {
std::pair<std::string,Boundbase*> opair;
opair.first = s.first;
opair.second = new Boundary<double>(
s.second[2],s.second[3]);
bounds.insert(opair);
}
break;
case 'i': {
std::pair<std::string,Boundbase*> opair;
opair.first = s.first;
opair.second = new Boundary<int>(
s.second[2],s.second[3]);
bounds.insert(opair);
break;
}
case 'c': {
std::pair<std::string,Boundbase*> opair;
opair.first = s.first;
opair.second = new Boundary<std::string>(
s.second[2],s.second[2]);
bounds.insert(opair);
break;
}
}
}
}
这可以使用 g++ 编译。当我尝试运行它时(如下):
int main() {
Data D;
Boundaries B;
std::ifstream iss("tphinit.txt");
D.read_lines(iss);
auto dbounds = D.get_xtypebound();
B.setboundmap(dbounds);
auto tbounds = B.getbounds();
auto sbound = tbounds["X1"];
std::cout << sbound->bpair.first << ","
<< sbound->bpair.second << std::endl;
}
我得到 'class Boundbase' has no member named 'bpair'
是真的,因为我指向的是基类而不是派生类。据我所知,尝试获取派生成员 bpair
需要我使用访问者模式。现在,很明显我是菜鸟,所以当我看到不同的做这件事的方法时,我有点不知所措(没有反思作者,只是因为我缺乏经验)。
所以我的主要问题是:这是最好和最简单的方法吗?如果可能的话,我想避免 boost::variant
(主要是为了纯度:这不会那么困难)。一个子问题是我是否必须使用访问者模式,或者是否有更好/更简单的方法来获取成员 pbair
?
我将不得不多次执行此查找,因此我希望尽可能快,但为了简单起见使用 stl
。
解决方法
使您的值在 3 种类型上成为标准变体。
如果失败,则提升变体。
Std 和 boost 变体确实是您想要的。您最终将实现其实现的某些子集。
如果做不到这一点,请查找有关如何实现其中一个的教程,或使用 std any。如果做不到这一点,则动态转换其他无用的包装器类型,并将虚拟 dtor 存储在唯一的 ptr 中,或者使用 try get 方法进行手动 RTTI。
然而,这只会变得越来越丑陋和/或效率低下。
Boost 变体和它的 std 变体是有原因的,这个原因是为了以有效的方式解决您所描述的确切问题。
#include <tuple>
#include <utility>
#include <string>
template<class...Ts>
struct destroy_helper {
std::tuple<Ts*...> data;
destroy_helper( std::tuple<Ts*...> d ):data(d){}
template<class T>
static void destroy(T* t){ t->~T(); }
template<std::size_t I>
void operator()(std::integral_constant<std::size_t,I>)const {
destroy( std::get<I>( data ) );
}
};
struct construct_helper {
template<class T,class...Args>
void operator()(T* target,Args&&...args)const {
::new( (void*)target ) T(std::forward<Args>(args)...);
}
};
template<std::size_t...Is>
struct indexes {};
template<std::size_t N,std::size_t...Is>
struct make_indexes:make_indexes<N-1,N-1,Is...> {};
template<std::size_t...Is>
struct make_indexes<0,Is...>{
using type=indexes<Is...>;
};
template<std::size_t N>
using make_indexes_t = typename make_indexes<N>::type;
template<class F>
void magic_switch( std::size_t i,indexes<>,F&& f ) {}
template<std::size_t I0,std::size_t...Is,class F>
void magic_switch( std::size_t i,indexes<I0,Is...>,F&& f )
{
if (i==I0) {
f( std::integral_constant<std::size_t,I0>{} );
return;
}
magic_switch( i,indexes<Is...>{},std::forward<F>(f) );
}
template<class T0>
constexpr T0 max_of( T0 t0 ) {
return t0;
}
template<class T0,class T1,class...Ts>
constexpr T0 max_of( T0 t0,T1 t1,Ts... ts ) {
return (t1 > t0)?max_of(t1,ts...):max_of(t0,ts...);
}
template<class...Ts>
struct Variant{
using Data=typename std::aligned_storage< max_of(sizeof(Ts)...),max_of(alignof(Ts)...)>::type;
std::size_t m_index=-1;
Data m_data;
template<std::size_t I>
using alternative_t=typename std::tuple_element<I,std::tuple<Ts...>>::type;
using pointers=std::tuple<Ts*...>;
using cpointers=std::tuple<Ts const*...>;
template<class T> T& get(){ return *reinterpret_cast<T*>(&m_data); }
template<class T> T const& get() const { return *reinterpret_cast<T*>(&m_data); }
template<std::size_t I>
alternative_t<I>& get(){ return std::get<I>(get_pointers()); }
template<std::size_t I>
alternative_t<I> const& get()const{ return std::get<I>(get_pointers()); }
pointers get_pointers(){
return pointers( (Ts*)&m_data... );
}
cpointers get_pointers()const{
return cpointers( (Ts const*)&m_data... );
}
std::size_t alternative()const{return m_index;}
void destroy() {
if (m_index == -1)
return;
magic_switch(m_index,make_indexes_t<sizeof...(Ts)>{},destroy_helper<Ts...>(get_pointers()));
}
template<std::size_t I,class...Args>
void emplace(Args&&...args) {
destroy();
construct_helper{}( std::get<I>(get_pointers()),std::forward<Args>(args)... );
m_index = I;
}
Variant()=default;
Variant(Variant const&)=delete;//todo
Variant&operator=(Variant const&)=delete;//todo
Variant(Variant &&)=delete;//todo
Variant&operator=(Variant &&)=delete;//todo
~Variant(){destroy();}
};
int main() {
Variant<int,double,std::string> bob;
bob.emplace<0>( 7 );
bob.emplace<1>( 3.14 );
bob.emplace<2>( "hello world" );
}
here is a really simple variant interface。
困难的部分是将运行时索引转换为您想要使用的编译时索引。我称之为魔法开关问题。
您可能还想实现应用访问者。
...
或者...
template<class T>
struct Derived;
struct Base {
virtual ~Base() {}
template<class T>
friend T* get(Base* base) {
Derived<T>* self = dynamic_cast<T*>(base);
return self?&self.t:nullptr;
}
template<class T>
friend T const* get(Base const* base) {
Derived<T> const* self = dynamic_cast<T const*>(base);
return self?&self.t:nullptr;
}
};
template<class T>
struct Derived:Base {
Derived(T in):t(std::move(in)){}
T t;
};
std::unordered_map<std::string,std::unique_ptr<Base>> map;
map["hello"] = std::unique_ptr<Base>( new Derived<int>(-1) );
map["world"] = std::unique_ptr<Base>( new Derived<double>(3.14) );
int* phello = get<int>(map["hello"]);
if (phello) std::cout << *hello << "\n";
double* pworld = get<double>(map["world"]);
if (pworld) std::cout << *world << "\n";
这是一个非常讨价还价的基础std::any
。