一亩三分地

 找回密码 注册账号

扫描二维码登录本站

最近看过此主题的会员


码农求职神器Triplebyte
不用海投
内推多家公司面试

Total Comp Calculator
输入offer信息
系统自动计算每年收入

科技公司如何
用数据分析驱动产品开发
coupon code 250off 立减$250

深入浅出AB Test
从入门到精通
coupon code 250off 立减$250
游戏初创公司招聘工程师、UIUX Designer和游戏策划
坐标湾区
DreamCraft创始团队
招聘游戏开发工程师
查看: 3174|回复: 24
收起左侧

吐槽一下c++

[复制链接] |试试Instant~
我的人缘0

分享帖子到朋友圈
yao zou 发表于 2019-5-20 15:28:54 | 显示全部楼层 |阅读模式
本楼: 👍   66% (4)
 
 
33% (2)   👎
全局: 👍   84% (191)
 
 
15% (34)    👎

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

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

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戰友
下一篇:请问:开车上下班的时候,听点儿什么好
我的人缘0
Airtnp 发表于 2019-5-20 22:16:19 | 显示全部楼层
本楼: 👍   100% (11)
 
 
0% (0)   👎
全局: 👍   91% (102)
 
 
8% (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 看不太懂但是超厉害的样子

查看全部评分

回复

使用道具 举报

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

[C++] 纯文本查看 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>

////////////////////////////////////////////////////////////////////////////////
/////////////////////////// 作为例子的简易Base类和两个子类 //////////////////////////
////////////////////////////////////////////////////////////////////////////////

class Base {
 public:
  virtual ~Base() {}
  virtual void Run() = 0;

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

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

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

class AnotherExample : public Base {
 public:
  AnotherExample(int value, const std::string& name)
      : value_(value), name_(name) {}

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

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

 private:
  int value_;
  std::string name_;
};

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

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

namespace meta_internal {

using EmptyList = TypeList<>;

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

template <typename TL, typename Out, std::size_t rest>
struct MetaTake<TL, Out, rest, std::enable_if_t<rest == 0>> {
  using type = Out;
};
template <typename TL, typename Out, std::size_t rest>
struct MetaTake<TL, Out, rest, std::enable_if_t<rest != 0>>
    : MetaTake<typename TL::tail,
               typename Out::template MetaPushBack<typename TL::head>,
               rest - 1> {};

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

template <typename TL, std::size_t rest>
struct MetaSkip<TL, rest, std::enable_if_t<rest == 0>> {
  using type = TL;
};
template <typename TL, std::size_t rest>
struct MetaSkip<TL, rest, std::enable_if_t<rest != 0>>
    : MetaSkip<typename TL::tail, rest - 1> {};

template <typename TL, typename TailTL>
struct MetaAppend;

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

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

template <typename Out, typename Rest, template <typename ...> class Op>
struct MetaFilter<Out, Rest, Op, std::enable_if_t<Rest::length == 0>> {
  using type = Out;
};
template <typename Out, typename Rest, template <typename ...> class Op>
struct MetaFilter<Out, Rest, Op,
                  std::enable_if_t<Op<typename Rest::head>::value>>
    : MetaFilter<typename Out::template MetaPushBack<typename Rest::head>,
                 typename Rest::tail, Op> {};
template <typename Out, typename Rest, template <typename ...> class Op>
struct MetaFilter<Out, Rest, Op,
                  std::enable_if_t<!Op<typename Rest::head>::value>>
    : MetaFilter<Out, typename Rest::tail, Op> {};

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

template <typename Out, typename Rest, template <typename ...> class Op>
struct MetaFlatmap<Out, Rest, Op,
                   std::enable_if_t<Rest::length == 0>> {
  using type = Out;
};
template <typename Out, typename Rest, template <typename ...> class Op>
struct MetaFlatmap<Out, Rest, Op,
                   std::enable_if_t<Rest::length != 0>>
    : MetaFlatmap<typename Out::template MetaAppend<
                      typename Op<typename Rest::head>::type>,
                  typename Rest::tail, Op> {};

template <typename LeftTL, typename RightTL>
struct MetaCartesianProduct {
  template <typename LeftT>
  struct LeftHelper {
    template <typename RightT>
    struct RightHelper {
      using type = TypeList<LeftT, RightT>;
    };
    using type = typename RightTL::template MetaApply<RightHelper>;
  };
  using type = typename LeftTL::template MetaFlatmap<LeftHelper>;
};

template <typename ...Ts>
class TypeListBase {
 public:
  // Members
  static constexpr std::size_t length = sizeof...(Ts);
  using type = TypeList<Ts...>;
  using self = type;

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

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

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

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

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

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

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

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

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

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

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

}  // namespace meta_internal

template <typename T, typename ...Ts>
class TypeList<T, Ts...> : public meta_internal::TypeListBase<T, Ts...> {
 public:
  using head = T;
  using tail = TypeList<Ts...>;
};

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

////////////////////////////////////////////////////////////////////////////////
//////////////////////// 带类型检查的Base::Instantiate实现 ////////////////////////
////////////////////////////////////////////////////////////////////////////////

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

template <typename SubclassWithArgList>
struct IsConstructibleWithArgTypes;

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

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

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

  if (instance == nullptr) {
    std::cout << "Runtime error: no matching constructor for "
              << "initialization of '" << name << "'\n";
  }
  return instance;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////// 测试样例 ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

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

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

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

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

  return 0;
}
回复

使用道具 举报

我的人缘0
qdlym 发表于 2019-6-16 17:13:58 | 显示全部楼层
本楼: 👍   100% (1)
 
 
0% (0)   👎
全局: 👍   91% (335)
 
 
8% (32)    👎
谢谢分享,学习了

评分

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

查看全部评分

回复

使用道具 举报

我的人缘0
magicsets 发表于 2019-5-22 00:10:06 | 显示全部楼层
本楼: 👍   100% (1)
 
 
0% (0)   👎
全局: 👍   98% (769)
 
 
1% (8)    👎
Airtnp 发表于 2019-5-21 17:34
这个其实就是std::variant... https://en.cppreference.com/w/cpp/utility/variant/variant

是说TypeList像std::variant么.. 我觉得完全不是一回事啊,首先那一堆做类型推导的meta functions和variant一点关系没有,你拿variant过来是没法使用std::is_constructible进行过滤以及条件性给出编译时错误的。

std::variant本质上就是一个功能有限的tagged union,顶多是ForEach那边“看起来有点像”搭配std::variant使用的std::visit —— 但也有很大的区别,因为std::visit是要拿对应类型的instance来dispatch的,而我们这里是根据运行时一个string的值(也就是"name"参数)进行dispatch,这个相关机制必须手写:要么用TypeList这个比较强大的工具,要么手写一个template recursive function。

当然这只是我的理解,如果你有什么巧妙的想法不妨试着实现一下,如果只是引用一两个概念别人可能没有办法理解你的方案,而且去实现一下自己才能知道是否行得通。
回复

使用道具 举报

我的人缘0
ourgit 发表于 2019-5-20 18:12:50 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   65% (395)
 
 
34% (209)    👎
看楼主的评论可以看出楼主很年轻啊
回复

使用道具 举报

我的人缘0
LB4195450681 发表于 2019-5-21 01:30:38 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   88% (8)
 
 
11% (1)    👎
static_assert(sizeof(...)==3, "Number of arguments is wrong); 了解下

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

使用道具 举报

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

使用道具 举报

我的人缘0
 楼主| yao zou 发表于 2019-5-21 10:32:43 | 显示全部楼层
本楼: 👍   0% (0)
 
 
0% (0)   👎
全局: 👍   84% (191)
 
 
15% (34)    👎
yema1986 发表于 2019-5-21 01:40
楼主可以尝试搜一下variadic template以及相关的template用法,比如std::isconstructible之类的。。

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

使用道具 举报

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

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

使用道具 举报

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

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

使用道具 举报

游客
请先登录
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

提醒:发帖可以选择内容隐藏,部分板块支持匿名发帖。请认真读完以下全部说明:

■隐藏内容方法 - 不要多加空格: [hide=200]你想要隐藏的内容比如面经[/hide]
■意思是:用户积分低于200则看不到被隐藏的内容
■可以自行设置积分值,不建议太高(200以上太多人看不到),也不建议太低(那就没必要隐藏了)
■建议只隐藏关键内容,比如具体的面试题目、涉及隐私的信息,大部分内容没必要隐藏。
■微信/QQ/电子邮件等,为防止将来被骚扰甚至人肉,以论坛私信方式发给对方最安全。
■匿名发帖的板块和方法:http://www.1point3acres.com/bbs/thread-405991-1-1.html

手机版|小黑屋|一亩三分地

GMT+8, 2019-7-21 14:54

Powered by Discuz! X3

© 2001-2013 Comsenz Inc. Design By HUXTeam

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