目录
前言
提问
bard给出的答案
AI答案的问题
要求bard改进
人类智能
AI VS 人类
前言
通过本文读者可以学到modern C++单例模式+工厂模式的混合体,同时也能看到:如今AI发展到了怎样的智能程度?怎样让AI帮助我们快速完成实现头脑中的想法?以及这样的智能程度会不会让程序员失业?
提问
这次我不用ChatGPT了,而是换成了bard.
mzhai: Write a Modern c++ program to implement a singleton factory to produce 2 products. Each product has different arguments, the 1st has only one parameter of type int, the 2nd product has two parameters int and string respectively.
这里我特意提出要创建两个参数不同的产品,防止AI写出来的类不通用(有点像避免算法过拟合的感觉)。
bard给出的答案
1 #include <iostream>2 #include <memory>3 #include <utility>45 enum class ProductType {6 Product1,7 Product28 };910 class Product {11 public:12 virtual ~Product() = default;13 virtual void use() = 0;14 };1516 // Concrete products with different arguments17 class Product1 : public Product {18 public:19 Product1(int arg1) : arg1_(arg1) {}20 void use() override { std::cout << "Using Product1 with arg1: " << arg1_ << std::endl; }21 private:22 int arg1_;23 };2425 class Product2 : public Product {26 public:27 Product2(int arg2_1, std::string arg2_2) : arg2_1_(arg2_1), arg2_2_(arg2_2) {}28 void use() override { std::cout << "Using Product2 with arg2_1: " << arg2_1_ << ", arg2_2: " << arg2_2_ << std::endl; }29 private:30 int arg2_1_;31 std::string arg2_2_;32 };3334 // Singleton factory with modern techniques35 class ProductFactory {36 public:37 static ProductFactory& getInstance() {38 static ProductFactory instance;39 return instance;40 }4142 template <typename... Args>43 std::unique_ptr<Product> createProduct(ProductType type, Args&&... args) {44 switch (type) {45 case ProductType::Product1:46 return std::make_unique<Product1>(std::forward<Args>(args)...);47 case ProductType::Product2:48 return std::make_unique<Product2>(std::forward<Args>(args)...);49 default:50 throw std::invalid_argument("Invalid product type");51 }52 }5354 private:55 ProductFactory() = default; // Private constructor for singleton56 };5758 int main() {59 ProductFactory& factory = ProductFactory::getInstance();6061 std::unique_ptr<Product> product1 = factory.createProduct(ProductType::Product1, 42);62 std::unique_ptr<Product> product2 = factory.createProduct(ProductType::Product2, 5, "Hello");6364 product1->use();65 product2->use();6667 return 0;68 }
AI答案的问题
第一次给出的答案编译失败,报错信息如下
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/unique_ptr.h:1065:34: error: no matching constructor for initialization of 'Product2'
1065 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:48:29: note: in instantiation of function template specialization 'std::make_unique<Product2, int>' requested here
48 | return std::make_unique<Product2>(std::forward<Args>(args)...);
| ^
有点奇怪吧,62行我们明明给Product2传的是两个参数,不是有Product2才走case ProductType::Product2吗(48行)?但编译器却抱怨给Product2的构造函数只传了一个int。这是为什么哪?
这是因为case是运行时代码,而createProduct根据61、62行会产生两个重载,每个都包含全部代码。比如61行会产生如下code:
std::unique_ptr<Product> createProduct(ProductType type, int arg) {switch (type) {case ProductType::Product1:return std::make_unique<Product1>(arg);case ProductType::Product2:return std::make_unique<Product2>(arg);default:throw std::invalid_argument("Invalid product type");}
}
因为Product2的构造函数有两个参数,而此处只有一个(int arg), 故找不到匹配的构造函数。
要求bard改进
直接把错误贴给他,它还是没能正真理解编译失败的原因,之所以这么说,是因为它给出的解决方案是:
case ProductType::Product2:if constexpr (sizeof...(Args) == 2) { // Ensure 2 arguments for Product2return std::make_unique<Product2>(std::forward<Args>(args)...);} else {throw std::invalid_argument("Invalid arguments for Product2");}
constexpr (sizeof...(Args) == 2) 确实在编译阶段就判断除了TRUE/FALSE:如果条件不符合,这段代码就不走了,从而导致61行实例出来的createProduct函数不报错,但62行依然会报错(看后面的cppinsights截图就会明白了)。我们暂且按照这种思路试一试, 虽然它有问题(后面会说)。
1 #include <iostream>2 #include <memory>3 #include <utility>45 class Product {6 public:7 virtual ~Product() = default;8 virtual void use() = 0;9 };1011 // Concrete products with different arguments12 class Product1 : public Product {13 public:14 Product1(int arg1) : arg1_(arg1) {}15 void use() override { std::cout << "Using Product1 with arg1: " << arg1_ << std::endl; }16 private:17 int arg1_;18 };1920 class Product2 : public Product {21 public:22 Product2(int arg2_1, std::string arg2_2) : arg2_1_(arg2_1), arg2_2_(arg2_2) {}23 void use() override { std::cout << "Using Product2 with arg2_1: " << arg2_1_ << ", arg2_2: " << arg2_2_ << std::endl; }24 private:25 int arg2_1_;26 std::string arg2_2_;27 };2829 class ProductFactory {30 public:31 static ProductFactory& getInstance() {32 static ProductFactory instance;33 return instance;34 }35 template <typename... Args>36 std::unique_ptr<Product> createProduct(Args&&... args) {37 if constexpr (sizeof...(Args) == 1){38 return std::make_unique<Product1>(std::forward<Args>(args)...);39 }40 if constexpr (sizeof...(Args) == 2){41 return std::make_unique<Product2>(std::forward<Args>(args)...);42 }43 }44 private:45 ProductFactory() = default; // Private constructor for singleton46 };4748 int main() {49 ProductFactory& factory = ProductFactory::getInstance();5051 std::unique_ptr<Product> product1 = factory.createProduct(42);52 std::string hi("Hello");53 std::unique_ptr<Product> product2 = factory.createProduct(5, hi);5455 product1->use();56 product2->use();5758 return 0;59 }
编译成功,运行也没问题。我们还可以通过cppinsights.io看看模板函数实例化出来的2个重载结果:
显然这种办法不好:
- 请想一想如果product1 和Product2都只有一个参数哪?就无法通过sizeof区分了。
- 而且,这么做产生了很多无用代码(if constexpr...)
人类智能
所以还是由我给它快速改一改吧。我们直接给createProduct函数多传一个模板参数进去,比如叫T,像下面这样:
template <typename T, typename... Args>
std::unique_ptr<T> createProduct(Args&&... args) {return std::make_unique<T>(std::forward<Args>(args)...);
}...std::unique_ptr<Product> product1 = factory.createProduct<Product1>(42);
std::string hi("Hello");
std::unique_ptr<Product> product2 = factory.createProduct<Product2>(5, hi);
看下createProduct如何实例化的?
问一下bard这么改怎么样?回答的井井有条!
mzhai:How about changing the code to above?
Bard: I'm happy to review the proposed code changes:
Key Changes:
- Templated
createProduct
function: ThecreateProduct
function now takes a template parameterT
to specify the product type, eliminating the need for theProductType
enum. (传了T就没必要传ProductType enum了)- Removal of
switch
statement: Theswitch
statement is no longer needed as the compiler determines the correct type based on the template argument.(createProduct中的Switch可以去掉了)- Explicit product type in
main
: ThecreateProduct
function is called with explicit template arguments (Product1
andProduct2
) to create the desired product types.(main中要显示指定产品类型,即尖括号中的Product1、Product2)Analysis:
- Pros:
- More concise and less verbose code. (代码简明)
- Leverages template metaprogramming for type-driven behavior.(利用了模板元编程)
- Potentially easier to extend for new product types.(新产品类型扩展更容易)
- Cons:
- Might be less readable for those less familiar with templates.(对不熟悉模板的人来说可读性变差)
- Error messages might be more cryptic due to template instantiation.(因为模板实例导致报错信息比较神秘不那么直接,确实!)
- Doesn't enforce a closed set of product types (could potentially create other types).(没有强制指定哪些产品类型能生产,有产生别的类型的风险)
Overall:
- The changes are valid and adhere to modern C++ practices.
- The choice between this approach and the previous one using the
ProductType
enum is mainly a matter of preference and trade-offs.- Consider the team's familiarity with templates and the specific project requirements when deciding on the best approach.
Additional Notes:
- Ensure that all product classes (
Product1
,Product2
, etc.) are derived from theProduct
base class for this approach to work correctly.- Consider using
static_assert
or concepts (if available in your C++ version) to enforce constraints on the template parameterT
, ensuring it's a valid product type.
AI VS 人类
AI确实智商还有限,但也确实能提高编程效率,copilot等等一般宣传能提高人类编程效率50%左右,在没有增量就业机会的情况下也就是一半工程师要失业了??
附上最终代码
便于大家COPY及实验。
#include <iostream>
#include <memory>
#include <utility>class Product {
public:virtual ~Product() = default;virtual void use() = 0;
};// Concrete products with different arguments
class Product1 : public Product {
public:Product1(int arg1) : arg1_(arg1) {}void use() override { std::cout << "Using Product1 with arg1: " << arg1_ << std::endl; }
private:int arg1_;
};class Product2 : public Product {
public:Product2(int arg2_1, std::string arg2_2) : arg2_1_(arg2_1), arg2_2_(arg2_2) {}void use() override { std::cout << "Using Product2 with arg2_1: " << arg2_1_ << ", arg2_2: " << arg2_2_ << std::endl; }
private:int arg2_1_;std::string arg2_2_;
};class ProductFactory {
public:static ProductFactory& getInstance() {static ProductFactory instance;return instance;}template <typename T, typename... Args>//typename std::enable_if<std::is_same<Product,T>::value, void>::typestd::unique_ptr<T> createProduct(Args&&... args) {return std::make_unique<T>(std::forward<Args>(args)...);}private:ProductFactory() = default; // Private constructor for singleton
};int main() {ProductFactory factory;std::unique_ptr<Product> product1 = factory.createProduct<Product1>(42);std::string hi("Hello");std::unique_ptr<Product> product2 = factory.createProduct<Product2>(5, hi);product1->use();product2->use();return 0;
}