目录
前言
提问
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>
4
5 enum class ProductType {
6 Product1,
7 Product2
8 };
9
10 class Product {
11 public:
12 virtual ~Product() = default;
13 virtual void use() = 0;
14 };
15
16 // Concrete products with different arguments
17 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 };
24
25 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 };
33
34 // Singleton factory with modern techniques
35 class ProductFactory {
36 public:
37 static ProductFactory& getInstance() {
38 static ProductFactory instance;
39 return instance;
40 }
41
42 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 }
53
54 private:
55 ProductFactory() = default; // Private constructor for singleton
56 };
57
58 int main() {
59 ProductFactory& factory = ProductFactory::getInstance();
60
61 std::unique_ptr<Product> product1 = factory.createProduct(ProductType::Product1, 42);
62 std::unique_ptr<Product> product2 = factory.createProduct(ProductType::Product2, 5, "Hello");
63
64 product1->use();
65 product2->use();
66
67 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 Product2
return 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>
4
5 class Product {
6 public:
7 virtual ~Product() = default;
8 virtual void use() = 0;
9 };
10
11 // Concrete products with different arguments
12 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 };
19
20 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 };
28
29 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 singleton
46 };
47
48 int main() {
49 ProductFactory& factory = ProductFactory::getInstance();
50
51 std::unique_ptr<Product> product1 = factory.createProduct(42);
52 std::string hi("Hello");
53 std::unique_ptr<Product> product2 = factory.createProduct(5, hi);
54
55 product1->use();
56 product2->use();
57
58 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>::type
std::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;
}