C++11 案例研究:如何使用智能指针实现工厂设计? [示例与测试]
C++11 case study: How to implement factory design with smartpointers? [Example and test]
这是一个在 C++11 中使用工厂设计模式的案例研究。如果有人能解释修复尝试中实际发生的事情,我将很高兴:
基本设计
假设我们想从工厂获取我们不想显式保存的元素,但我们想确保它们被正确删除。我用谷歌搜索了示例,但我似乎没有找到任何示例,所以我们需要做一些测试:
我认为我们可以实现目标的方法是使用 std::unique_ptr 对象来处理所有删除任务。我在这里只喜欢 unique_ptr 而不是 shared_ptr,因为它会在离开范围时调用持有元素的析构函数,因此使用它会使任何隐藏的复制可见。首先让我们创建一个能够创建 lambda 函数的简单工厂:
class To_do_base
{
public:
std :: function<void()> what_to_do;
To_do_base() {};
virtual ~To_do_base() { std :: cout << "~To_do_base()..." << std :: endl; };
std :: function<void()>& get_function() { return this -> what_to_do; };
};
class Special_to_do : public To_do_base
{
public:
Special_to_do()
{
this -> what_to_do = []()
{
std :: cout << "I have a special thing for you to do!" << std :: endl;
};
};
~Special_to_do() { std :: cout << "~Special_to_do()..." << std :: endl; };
};
所以现在每当我们调用 Special_to_do() 构造函数时,我们都会生成一个函数来打印 "I have a special thing for you to do!".
现在让我们创建一个工厂来为我们实例化这些 类:
class To_do_factory
{
public:
static std :: unique_ptr<To_do_base> get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
return std :: unique_ptr<To_do_base>( new Special_to_do() );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
return nullptr;
};
};
现在让我们创建一个 main() 并祈祷吧!
int main()
{
To_do_factory :: get_to_do( "special" ) -> get_function()();
return 0;
}
程序输出:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
问题
到目前为止一切顺利。但是让我们通过先保存函数然后再调用它来仔细检查结果:
int main()
{
std :: function<void()> to_do = To_do_factory :: get_to_do( "special" ) -> get_function();
to_do();
return 0;
}
程序输出:
~Special_to_do()...
~To_do_base()...
I have a special thing for you to do!
所以看起来return值现在实际上是最初创建的函数的副本。为什么这很糟糕?通常,当您传递 lambda 函数时,它们会捕获其范围内的一些变量(例如 this)。但是,在复制它们时,这些变量可能会超出范围,从而导致不太严重的分段错误。
我有一个答案,其中我写下了我到目前为止推断的所有内容,以及 "hack" 这个设计的方法。但我不知道我在那里做了什么,这只是一个案例研究。请帮助我了解那里出了什么问题。谢谢!
正在尝试解决问题
测试 get_function() return 值
可能不是智能指针的问题,而是get_function()的return值。我们应该测试一下:
int main()
{
To_do_base* a = new Special_to_do();
std :: function<void()> to_do = a -> get_function();
to_do();
delete a;
return 0;
}
这个程序输出:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
这是正确的行为。
试图通过移动 unique_ptr 来强行通过它
也许我们应该以某种方式尝试强制工厂 return 原始对象。也许通过对 return 值执行 std::move() 来 returning 一个右值可以帮助:
class To_do_factory
{
public:
static std :: unique_ptr<To_do_base>&& get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
return std :: move( std :: unique_ptr<To_do_base>( new Special_to_do() ) );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
exit( -1 );
return std :: move( std :: unique_ptr<To_do_base>( nullptr ) );
};
};
代码编译,输出为:
~Special_to_do()...
~To_do_base()...
[1] 329 segmentation fault ./main
嗯,这有点尴尬......如果我们先保存右值然后调用它呢?检查后:不传递右值引用就好了,但它仍然会用右值创建分段错误。
在 main()
中保存 unique_ptr
所以让我们回到左值。此代码与工厂的第一种方法:
int main()
{
std :: unique_ptr<To_do_base> saved_return_value( To_do_factory :: get_to_do( "special" ) );
std :: cout << "Return value saved." << std :: endl;
//std :: function<void()> to_do = To_do_factory :: get_to_do( "special" ) -> get_function(); // WRONG: assigns by copying OR frees dinamically allocated memory after assigning
std :: function<void()> to_do = saved_return_value -> get_function();
to_do();
return 0;
}
生成良好的输出:
Return value saved.
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
好吧,它工作得很好......但是它有点失去了拥有功能工厂的意义。在这里我们能够生成工厂元素,但只有在我们保存它们时...
在工厂中保存指针
也许有一种方法可以在工厂中保存指针。我们应该尝试在工厂中创建一个静态成员变量来处理保存。 std :: vector 应该没问题。
class To_do_factory
{
public:
static std :: vector<std :: unique_ptr<To_do_base>> to_do_list;
static std :: unique_ptr<To_do_base>& get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
to_do_list.push_back( std :: unique_ptr<To_do_base>( new Special_to_do ) );
return to_do_list.back();
//return std :: unique_ptr<To_do_base>( new Special_to_do() );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
exit( -1 );
return to_do_list.back();
};
};
std :: vector<std :: unique_ptr<To_do_base>> To_do_factory :: to_do_list;
// global scope I have no idea, where else could I define this...
输入是:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
终于可以输出了! :)
测试
我们不要太过分了,我们应该测试 lambda returned 是否真的是我们想要的。我们可以尝试给它一个值来捕获:
class Special_to_do : public To_do_base
{
private:
int x = 2;
public:
Special_to_do()
{
this -> what_to_do = [&]()
{
std :: cout << "I have " << this -> x << " special thing for you to do!" << std :: endl;
};
};
~Special_to_do() { std :: cout << "~Special_to_do()..." << std :: endl; };
};
输出:
I have 2 special thing for you to do!
~Special_to_do()...
~To_do_base()...
这是预期的输出。我仍然对这种行为感到困惑......任何人都可以告诉我 "more correct" 实现这个的方法吗?
谢谢:)
如果我理解,问题是如果存储、复制等,如何防止 lambda 捕获的变量超出范围(已经被破坏)
这里有两个建议。
Lambda 经常回调。在超出范围之前,对象从将回调它的服务中注销自己。
或者,lambda 捕获的所有指针(其生命周期显然不安全)应该是 shared_ptrs。如果在调用 lambda 时对象确实始终可用,那么这很好。您可能会发现 http://en.cppreference.com/w/cpp/memory/enable_shared_from_this 有帮助的讨论。
这是一个在 C++11 中使用工厂设计模式的案例研究。如果有人能解释修复尝试中实际发生的事情,我将很高兴:
基本设计
假设我们想从工厂获取我们不想显式保存的元素,但我们想确保它们被正确删除。我用谷歌搜索了示例,但我似乎没有找到任何示例,所以我们需要做一些测试:
我认为我们可以实现目标的方法是使用 std::unique_ptr 对象来处理所有删除任务。我在这里只喜欢 unique_ptr 而不是 shared_ptr,因为它会在离开范围时调用持有元素的析构函数,因此使用它会使任何隐藏的复制可见。首先让我们创建一个能够创建 lambda 函数的简单工厂:
class To_do_base
{
public:
std :: function<void()> what_to_do;
To_do_base() {};
virtual ~To_do_base() { std :: cout << "~To_do_base()..." << std :: endl; };
std :: function<void()>& get_function() { return this -> what_to_do; };
};
class Special_to_do : public To_do_base
{
public:
Special_to_do()
{
this -> what_to_do = []()
{
std :: cout << "I have a special thing for you to do!" << std :: endl;
};
};
~Special_to_do() { std :: cout << "~Special_to_do()..." << std :: endl; };
};
所以现在每当我们调用 Special_to_do() 构造函数时,我们都会生成一个函数来打印 "I have a special thing for you to do!". 现在让我们创建一个工厂来为我们实例化这些 类:
class To_do_factory
{
public:
static std :: unique_ptr<To_do_base> get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
return std :: unique_ptr<To_do_base>( new Special_to_do() );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
return nullptr;
};
};
现在让我们创建一个 main() 并祈祷吧!
int main()
{
To_do_factory :: get_to_do( "special" ) -> get_function()();
return 0;
}
程序输出:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
问题
到目前为止一切顺利。但是让我们通过先保存函数然后再调用它来仔细检查结果:
int main()
{
std :: function<void()> to_do = To_do_factory :: get_to_do( "special" ) -> get_function();
to_do();
return 0;
}
程序输出:
~Special_to_do()...
~To_do_base()...
I have a special thing for you to do!
所以看起来return值现在实际上是最初创建的函数的副本。为什么这很糟糕?通常,当您传递 lambda 函数时,它们会捕获其范围内的一些变量(例如 this)。但是,在复制它们时,这些变量可能会超出范围,从而导致不太严重的分段错误。
我有一个答案,其中我写下了我到目前为止推断的所有内容,以及 "hack" 这个设计的方法。但我不知道我在那里做了什么,这只是一个案例研究。请帮助我了解那里出了什么问题。谢谢!
正在尝试解决问题
测试 get_function() return 值
可能不是智能指针的问题,而是get_function()的return值。我们应该测试一下:
int main()
{
To_do_base* a = new Special_to_do();
std :: function<void()> to_do = a -> get_function();
to_do();
delete a;
return 0;
}
这个程序输出:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
这是正确的行为。
试图通过移动 unique_ptr 来强行通过它
也许我们应该以某种方式尝试强制工厂 return 原始对象。也许通过对 return 值执行 std::move() 来 returning 一个右值可以帮助:
class To_do_factory
{
public:
static std :: unique_ptr<To_do_base>&& get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
return std :: move( std :: unique_ptr<To_do_base>( new Special_to_do() ) );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
exit( -1 );
return std :: move( std :: unique_ptr<To_do_base>( nullptr ) );
};
};
代码编译,输出为:
~Special_to_do()...
~To_do_base()...
[1] 329 segmentation fault ./main
嗯,这有点尴尬......如果我们先保存右值然后调用它呢?检查后:不传递右值引用就好了,但它仍然会用右值创建分段错误。
在 main()
中保存 unique_ptr所以让我们回到左值。此代码与工厂的第一种方法:
int main()
{
std :: unique_ptr<To_do_base> saved_return_value( To_do_factory :: get_to_do( "special" ) );
std :: cout << "Return value saved." << std :: endl;
//std :: function<void()> to_do = To_do_factory :: get_to_do( "special" ) -> get_function(); // WRONG: assigns by copying OR frees dinamically allocated memory after assigning
std :: function<void()> to_do = saved_return_value -> get_function();
to_do();
return 0;
}
生成良好的输出:
Return value saved.
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
好吧,它工作得很好......但是它有点失去了拥有功能工厂的意义。在这里我们能够生成工厂元素,但只有在我们保存它们时...
在工厂中保存指针
也许有一种方法可以在工厂中保存指针。我们应该尝试在工厂中创建一个静态成员变量来处理保存。 std :: vector 应该没问题。
class To_do_factory
{
public:
static std :: vector<std :: unique_ptr<To_do_base>> to_do_list;
static std :: unique_ptr<To_do_base>& get_to_do( const std :: string& keyword_p )
{
if( keyword_p == "special" )
{
to_do_list.push_back( std :: unique_ptr<To_do_base>( new Special_to_do ) );
return to_do_list.back();
//return std :: unique_ptr<To_do_base>( new Special_to_do() );
}
std :: cerr << "Error: Argument_factory::get_argument()..." << std :: endl;
exit( -1 );
return to_do_list.back();
};
};
std :: vector<std :: unique_ptr<To_do_base>> To_do_factory :: to_do_list;
// global scope I have no idea, where else could I define this...
输入是:
I have a special thing for you to do!
~Special_to_do()...
~To_do_base()...
终于可以输出了! :)
测试
我们不要太过分了,我们应该测试 lambda returned 是否真的是我们想要的。我们可以尝试给它一个值来捕获:
class Special_to_do : public To_do_base
{
private:
int x = 2;
public:
Special_to_do()
{
this -> what_to_do = [&]()
{
std :: cout << "I have " << this -> x << " special thing for you to do!" << std :: endl;
};
};
~Special_to_do() { std :: cout << "~Special_to_do()..." << std :: endl; };
};
输出:
I have 2 special thing for you to do!
~Special_to_do()...
~To_do_base()...
这是预期的输出。我仍然对这种行为感到困惑......任何人都可以告诉我 "more correct" 实现这个的方法吗? 谢谢:)
如果我理解,问题是如果存储、复制等,如何防止 lambda 捕获的变量超出范围(已经被破坏)
这里有两个建议。
Lambda 经常回调。在超出范围之前,对象从将回调它的服务中注销自己。
或者,lambda 捕获的所有指针(其生命周期显然不安全)应该是 shared_ptrs。如果在调用 lambda 时对象确实始终可用,那么这很好。您可能会发现 http://en.cppreference.com/w/cpp/memory/enable_shared_from_this 有帮助的讨论。