查看: 5968| 回复: 24
收起左侧

吐槽一下c++

已注销-2317 | 显示全部楼层
本楼:   👍  4
67%
33%
2   👎
全局:   3918
91%
9%
395

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
实在是受不了了我一定要吐槽一下这个语言。
最近在做一个项目,有一个需求是写工厂模式,然而基类的不同子类构造函数参数会有区别,所以不能直接写个create()。 于是楼主谷歌了一下,发现了这一招https://stackoverflow.com/a/18493862

结果,刚开始用的时候感觉还不错,用着用着就发现问题了。如果调用instantiate()的时候参数传错了,那么无论是编译期还是运行期都*不会*报任何错误!比如说,你要是把
Base * q = Base::instantiate("AnotherExample", 10, "Mahfuza");
写成了
Base * q = Base::instantiate("AnotherExample");
这个程序能看上去很正常地运行下去,然而实际上你没传的那两个参数,在AnotherExample的构造函数里面,它们的值都是未定义值:)也就是这个程序会出现很难排查的隐性bug。楼主刚刚就被坑了两个小时,感觉程序写的都没什么问题但是算出来的数据精度就是比预期的低。

类似的问题经历过好几次,比如说C++11里面加了个特性,虚函数可以支持covariant return type,也就是当你基类函数返回Base *的时候,子类函数可以返回Derived *。如果你想用一波同样也是C++11特性的智能指针的化,你就会发现,智能指针不能支持covariant return type......也就是,如果基类函数返回了shared_ptr<Base>,子类函数只能返回shared_ptr<Base>,不能返回shared_ptr<Derived>

同样,C++ 11的shared_ptr类型有个make_shared,它接一坨模板参数,并且根据模板参数去调用对应的构造函数。同时C++11还弄了个initialization_list,可以写类似于vector{1, 2, 3}的语法。可是,问题在于,这个make_shared它的模板匹配不了initialization_list......,就算你类A有个构造函数能接initialization_list,你也不能make_shared<A>({1, 2, 3}),不然你就会看到一坨看不懂的template编译错误

现在觉得C++这个语言就是一大坨feature的集合,然而这些feature总是有这样那样的问题,要么是不大能合起来用,要么是用起来需要很小心不然undefined behavior分分钟教你做人:)

楼主写Python的时候感觉写起来都很爽,然而面对C++,只能觉得自己太弱了。


上一篇:求Fortran戰友
下一篇:请问:开车上下班的时候,听点儿什么好
Airtnp 2019-5-20 22:16:19 | 显示全部楼层
本楼:   👍  13
100%
0%
0   👎
全局:   287
97%
3%
9
1. 你自己写的undefined behavior瞎转指针..谁也保证不了

2. 因为A<T>和T没有什么关系,不像Rust可以自动根据member推导covariant/contravariant

3. initializer_list本来就是推导出个common_type...不然你就需要一个中间initalizer_list<any>造成无穷无尽的standard扯皮/exception safety/编译器问题

评分

参与人数 1大米 +2 收起 理由
莫可可小姐 + 2 看不太懂但是超厉害的样子

查看全部评分

回复

使用道具 举报

magicsets 2019-5-21 16:36:26 | 显示全部楼层
本楼:   👍  2
100%
0%
0   👎
全局:   1438
98%
2%
30
第一个问题要带类型检查的话其实非常麻烦,因为需要在编译期做很多事情,我写了一份参考代码,测试环境用的Clang编译器和C++14,楼主有兴趣的话可以研究一下..

  1. #include <iostream>
  2. #include <memory>
  3. #include <string>
  4. #include <type_traits>
  5. #include <utility>

  6. ////////////////////////////////////////////////////////////////////////////////
  7. /////////////////////////// 作为例子的简易Base类和两个子类 //////////////////////////
  8. ////////////////////////////////////////////////////////////////////////////////

  9. class Base {
  10. public:
  11.   virtual ~Base() {}
  12.   virtual void Run() = 0;

  13.   template <typename... Ts>
  14.   static Base* Instantiate(const std::string& name, Ts&&... args);
  15. };

  16. class DerivedExample : public Base {
  17. public:
  18.   void Run() override { std::cout << "DerivedExample\n"; }

  19.   static std::string GetName() { return "DerivedExample"; }
  20. };

  21. class AnotherExample : public Base {
  22. public:
  23.   AnotherExample(int value, const std::string& name)
  24.       : value_(value), name_(name) {}

  25.   void Run() override {
  26.     std::cout << "AnotherExample " << value_ << " " << name_ << "\n";
  27.   }

  28.   static std::string GetName() { return "AnotherExample"; }

  29. private:
  30.   int value_;
  31.   std::string name_;
  32. };

  33. ////////////////////////////////////////////////////////////////////////////////
  34. ////////// 用于compile-time类型计算的TypeList, 这里只实现了一小部分相关功能 ////////////
  35. ////////////////////////////////////////////////////////////////////////////////

  36. // 这部分代码虽然写了一大堆,但主要只是为了搭建出 MetaFilter 和 MetaCartesianProduct
  37. // 这两个meta method 以及 ForEach 这一个static method,可以先跳过..
  38. template <typename ...Ts> class TypeList;

  39. namespace meta_internal {

  40. using EmptyList = TypeList<>;

  41. template <typename TL, typename Out, std::size_t rest, typename Enable = void>
  42. struct MetaTake;

  43. template <typename TL, typename Out, std::size_t rest>
  44. struct MetaTake<TL, Out, rest, std::enable_if_t<rest == 0>> {
  45.   using type = Out;
  46. };
  47. template <typename TL, typename Out, std::size_t rest>
  48. struct MetaTake<TL, Out, rest, std::enable_if_t<rest != 0>>
  49.     : MetaTake<typename TL::tail,
  50.                typename Out::template MetaPushBack<typename TL::head>,
  51.                rest - 1> {};

  52. template <typename TL, std::size_t rest, typename Enable = void>
  53. struct MetaSkip;

  54. template <typename TL, std::size_t rest>
  55. struct MetaSkip<TL, rest, std::enable_if_t<rest == 0>> {
  56.   using type = TL;
  57. };
  58. template <typename TL, std::size_t rest>
  59. struct MetaSkip<TL, rest, std::enable_if_t<rest != 0>>
  60.     : MetaSkip<typename TL::tail, rest - 1> {};

  61. template <typename TL, typename TailTL>
  62. struct MetaAppend;

  63. template <typename ...Ts, typename ...Tails>
  64. struct MetaAppend<TypeList<Ts...>, TypeList<Tails...>> {
  65.   using type = TypeList<Ts..., Tails...>;
  66. };

  67. template <typename Out, typename Rest, template <typename ...> class Op,
  68.           typename Enable = void>
  69. struct MetaFilter;

  70. template <typename Out, typename Rest, template <typename ...> class Op>
  71. struct MetaFilter<Out, Rest, Op, std::enable_if_t<Rest::length == 0>> {
  72.   using type = Out;
  73. };
  74. template <typename Out, typename Rest, template <typename ...> class Op>
  75. struct MetaFilter<Out, Rest, Op,
  76.                   std::enable_if_t<Op<typename Rest::head>::value>>
  77.     : MetaFilter<typename Out::template MetaPushBack<typename Rest::head>,
  78.                  typename Rest::tail, Op> {};
  79. template <typename Out, typename Rest, template <typename ...> class Op>
  80. struct MetaFilter<Out, Rest, Op,
  81.                   std::enable_if_t<!Op<typename Rest::head>::value>>
  82.     : MetaFilter<Out, typename Rest::tail, Op> {};

  83. template <typename Out, typename Rest, template <typename ...> class Op,
  84.           typename Enable = void>
  85. struct MetaFlatmap;

  86. template <typename Out, typename Rest, template <typename ...> class Op>
  87. struct MetaFlatmap<Out, Rest, Op,
  88.                    std::enable_if_t<Rest::length == 0>> {
  89.   using type = Out;
  90. };
  91. template <typename Out, typename Rest, template <typename ...> class Op>
  92. struct MetaFlatmap<Out, Rest, Op,
  93.                    std::enable_if_t<Rest::length != 0>>
  94.     : MetaFlatmap<typename Out::template MetaAppend<
  95.                       typename Op<typename Rest::head>::type>,
  96.                   typename Rest::tail, Op> {};

  97. template <typename LeftTL, typename RightTL>
  98. struct MetaCartesianProduct {
  99.   template <typename LeftT>
  100.   struct LeftHelper {
  101.     template <typename RightT>
  102.     struct RightHelper {
  103.       using type = TypeList<LeftT, RightT>;
  104.     };
  105.     using type = typename RightTL::template MetaApply<RightHelper>;
  106.   };
  107.   using type = typename LeftTL::template MetaFlatmap<LeftHelper>;
  108. };

  109. template <typename ...Ts>
  110. class TypeListBase {
  111. public:
  112.   // Members
  113.   static constexpr std::size_t length = sizeof...(Ts);
  114.   using type = TypeList<Ts...>;
  115.   using self = type;

  116.   // Meta methods
  117.   template <typename Type>
  118.   using MetaPushBack = TypeList<Ts..., Type>;

  119.   template <std::size_t n>
  120.   using MetaTake = typename MetaTake<self, EmptyList, n>::type;

  121.   template <std::size_t n>
  122.   using MetaSkip = typename MetaSkip<self, n>::type;

  123.   template <typename OtherTypeList>
  124.   using MetaAppend = typename MetaAppend<self, OtherTypeList>::type;

  125.   template <template <typename ...> class Op>
  126.   using MetaApply = TypeList<typename Op<Ts>::type...>;

  127.   template <template <typename ...> class Op>
  128.   using MetaFilter = typename MetaFilter<EmptyList, self, Op>::type;

  129.   template <template <typename ...> class Op>
  130.   using MetaFlatmap = typename MetaFlatmap<EmptyList, self, Op>::type;

  131.   template <typename TL>
  132.   using MetaCartesianProduct = typename MetaCartesianProduct<self, TL>::type;

  133.   // Static methods
  134.   template <typename Functor>
  135.   static inline void ForEach(const Functor &functor) {
  136.     ForEachInternal<length == 0>(functor);
  137.   }

  138. private:
  139.   template <bool kEmpty, typename Functor,
  140.             std::enable_if_t<!kEmpty> * = nullptr>
  141.   static inline void ForEachInternal(const Functor &functor) {
  142.     functor(MetaTake<1>());
  143.     MetaSkip<1>::ForEach(functor);
  144.   }

  145.   template <bool kEmpty, typename Functor,
  146.             std::enable_if_t<kEmpty> * = nullptr>
  147.   static inline void ForEachInternal(const Functor &functor) { /* No-op */ }
  148. };

  149. }  // namespace meta_internal

  150. template <typename T, typename ...Ts>
  151. class TypeList<T, Ts...> : public meta_internal::TypeListBase<T, Ts...> {
  152. public:
  153.   using head = T;
  154.   using tail = TypeList<Ts...>;
  155. };

  156. template <>
  157. class TypeList<> : public meta_internal::TypeListBase<> {};

  158. ////////////////////////////////////////////////////////////////////////////////
  159. //////////////////////// 带类型检查的Base::Instantiate实现 ////////////////////////
  160. ////////////////////////////////////////////////////////////////////////////////

  161. using SubclassList = TypeList<DerivedExample,
  162.                               AnotherExample /* ... 在这里注册所有的子类 ... */>;

  163. template <typename SubclassWithArgList>
  164. struct IsConstructibleWithArgTypes;

  165. template <typename Subclass, typename... ArgTypes>
  166. struct IsConstructibleWithArgTypes<TypeList<Subclass, TypeList<ArgTypes...>>> {
  167.   static constexpr bool value =
  168.       std::is_constructible<Subclass, ArgTypes...>::type::value;
  169. };

  170. template <typename... Ts>
  171. Base* Base::Instantiate(const std::string& name, Ts&&... args) {
  172.   // 在编译时枚举所有SubclassList中的类型(也就是Base的子类)
  173.   // 并筛选出对于变长参数类型Ts...拥有合法构造函数的子类,放在Candidates里
  174.   using Candidates =
  175.       typename SubclassList::MetaCartesianProduct<TypeList<TypeList<Ts...>>>
  176.                            ::template MetaFilter<IsConstructibleWithArgTypes>;
  177.   // 如果没有任何一个子类可以接收Ts...类型,那么直接可以在编译时报错
  178.   static_assert(Candidates::length != 0,
  179.                 "Compile-time error: no matching constructor for the "
  180.                 "specified arguments");

  181.   // 否则,只有在运行时看到name的值的时候,我们才能知道构造参数是否是对应name的那个子类的
  182.   Base* instance = nullptr;
  183.   // 枚举Candidates里的所有子类,如果其名字和name一致的话,那么由前面的筛选可知参数一定是合法的
  184.   Candidates::ForEach([&](auto e) {
  185.     using Subclass = typename decltype(e)::head::head;
  186.     if (name != Subclass::GetName()) {
  187.       return;
  188.     }
  189.     if (instance != nullptr) {
  190.       std::cout << "Runtime error: duplicated subclass name '"
  191.                 << name << "'\n";
  192.     }
  193.     instance = new Subclass(std::forward<Ts>(args)...);
  194.   });

  195.   if (instance == nullptr) {
  196.     std::cout << "Runtime error: no matching constructor for "
  197.               << "initialization of '" << name << "'\n";
  198.   }
  199.   return instance;
  200. }

  201. ////////////////////////////////////////////////////////////////////////////////
  202. //////////////////////////////////// 测试样例 ////////////////////////////////////
  203. ////////////////////////////////////////////////////////////////////////////////

  204. int main(int argc, char *argv[]) {
  205.   Base* first = Base::Instantiate("DerivedExample");
  206.   // 输出:DerivedExample
  207.   first->Run();

  208.   Base* second = Base::Instantiate("AnotherExample", 123, "Hello world");
  209.   // 输出:AnotherExample 123 Hello world
  210.   second->Run();

  211.   // 编译时错误:no matching constructor for the specified arguments
  212.   // Base* third = Base::Instantiate("DerivedExample", 123);

  213.   // 运行时报错:no matching constructor for initialization of 'AnotherExample'
  214.   Base* fourth = Base::Instantiate("AnotherExample");
  215.   // 输出:1
  216.   std::cout << (fourth == nullptr) << "\n";

  217.   return 0;
  218. }
复制代码
回复

使用道具 举报

qdlym 2019-6-16 17:13:58 | 显示全部楼层
本楼:   👍  1
100%
0%
0   👎
全局:   606
92%
8%
52
谢谢分享,学习了

评分

参与人数 1大米 +1 收起 理由
qdaudioqd + 1 给你点个赞!

查看全部评分

回复

使用道具 举报

ourgit 2019-5-20 18:12:50 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   6496
79%
21%
1703
看楼主的评论可以看出楼主很年轻啊
回复

使用道具 举报

falah325 2019-5-21 01:30:38 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   34
97%
3%
1
static_assert(sizeof(...)==3, "Number of arguments is wrong); 了解下

C++的确有时候语法很扯淡。但是你要首先学会正确写法和用法啊。。。
回复

使用道具 举报

yema1986 2019-5-21 01:40:10 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   120
98%
2%
2
楼主可以尝试搜一下variadic template以及相关的template用法,比如std::isconstructible之类的。。
回复

使用道具 举报

luluchi 2019-5-21 04:36:59 | 显示全部楼层
本楼:    0
0%
0%
0  
全局:   330
95%
5%
19
真 头疼c++
回复

使用道具 举报

 楼主| 已注销-2317 2019-5-21 10:32:43 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   3918
91%
9%
395
yema1986 发表于 2019-5-21 01:40
楼主可以尝试搜一下variadic template以及相关的template用法,比如std::isconstructible之类的。。

感觉这里不太能用这个
主要是,你需要把所有的create都存到一个registry里面,然后这些函数的参数都是不一样的。那这样的化registry的value就必须是void*了。c++应该没有那种可以表示所有函数的父类的那种类型吧
回复

使用道具 举报

Airtnp 2019-5-21 12:51:41 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   287
97%
3%
9
yao zou 发表于 2019-5-21 10:32
感觉这里不太能用这个
主要是,你需要把所有的create都存到一个registry里面,然后这些函数的参数都是不 ...

实际上你需要的是运行时的类型信息,可以试着用RTTI (typeid),或者放宽约束只存ctor需要的参数个数,当然都需要额外的overhead,因为本来类型信息编译时就抹除了。
回复

使用道具 举报

Airtnp 2019-5-21 17:34:22 | 显示全部楼层
本楼:   👍  0
0%
0%
0   👎
全局:   287
97%
3%
9
magicsets 发表于 2019-5-21 16:36
第一个问题要带类型检查的话其实非常麻烦,因为需要在编译期做很多事情,我写了一份参考代码,测试环境用的 ...

这个其实就是std::variant... https://en.cppreference.com/w/cpp/utility/variant/variant
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

Advertisement
>
快速回复 返回顶部 返回列表