在尝试一些C++'s语言特性时,我通过使用lambda成功地设计了一个无法调用的函数调用,它一般地创建一个任意类包装器,该类包装器返回类对象,而不必在它自己的独立翻译单元中直接实现该类。包装类本身仍然包含在一个翻译单元中,因为它是在lambda的范围内声明和定义的。这个lambda然后通过使用模板类型推断返回这个类型。
在我的例子中,我在类型T对象周围创建了一个包装类,因为它存储了指向该对象的指针及其构造函数和析构函数,分别对该指针调用了new和delete。成员是私有的,只能通过类的公共接口访问,并且它们被声明为不可修改的const,这意味着调用这些方法的使用不会改变生成的类对象的状态。为了更好地说明这一点,下面是我的源代码:
#include <string>
#include <string_view>
template<typename T>
auto create_dynamic_wrapper = [&](const std::string_view name, T value) {
class DynamicWrapper {
public:
DynamicWrapper() = default;
DynamicWrapper(const std::string_view name_in, T value) : name_{ name_in } {
this->pData_ = new T{ value };
}
~DynamicWrapper() {
if (nullptr != this->pData_) {
delete this->pData_;
this->pData_ = nullptr;
}
}
// define copy constructor and assignment operator here
// wrt how you want this wrapper class to behave in order
// to preserve the rule of 3 or 5. Meaning, do you want
// to allow this class object to be copyable or not...
auto value() const { return *(this->pData_); }
auto ptr() const { return this->pData_; }
auto name() const { return this->name_; }
private:
T* pData_ = nullptr;
const std::string name_;
};
return DynamicWrapper(name, value);
}; 驱动程序
#include <iostream>
#include "some.h"
int main() {
auto C = create_dynamic_wrapper<int>("Foo", 7);
std::cout << C.name() << '\n';
std::cout << C.ptr() << '\n';
std::cout << C.value() << '\n';
std::cout << typeid(C).name() << '\n';
return 0;
}当我在我的机器上运行这个程序时,我得到以下输出:
Foo
00000000006857F0
7
class `public: __cdecl <lambda_986b701ab3be1e8cd3d0d2d875c96c7d>::operator()(class std::basic_string_view<char,struct st
d::char_traits<char> >,int)const __ptr64'::`2'::DynamicWrapper第二行将因使用动态堆内存分配而发生变化。第四行因命名约定、名称损坏、符号生成等原因而有所不同,取决于编译器、操作系统和其他因素。
现在谈谈我的问题和关切..。
smart-pointers的情况下,当涉及到在这个上下文中定义的自包含对象中使用new和delete时,我还需要知道其他什么吗?-Note给读者-
关于这个代码结构,我觉得有些地方很有趣。lambda本身在内部和本地声明和定义一个名为DynamicWrapper<T>的类对象,其中定义和声明该类在此lambda的范围内,并通过使用auto type deduction返回该类型的实例化类实例。
然后,在调用和调用此lambda的某个转换单元中,通过使用auto说明符,返回到用户声明的auto variable中的命名对象实际上是一个DynamicWrapper<T>实例,即使在被调用的lambda之外不存在这样的类声明或定义。在总体设计模式和实现方面,我发现这是一组非常有趣的属性和行为。
发布于 2021-02-06 02:41:48
我不知道这种类型的代码结构/生成是否有特定的惯用名称。如果是这样的话,这个成语会叫什么?
我自己也不确定。
这被认为是一个定义明确的计划吗?
在我看来不错。
使用这种结构意味着什么:
看起来身材很好。
在一般情况下没有。
在这种特殊情况下:您没有实现3/5的规则,而是管理类内动态创建的资源。因此,绝对有可能出现资源处理不当的情况。
这会被认为是线程和异常安全吗?
没有任何东西是线程安全的,除非您显式地这样做(除了专门为线程设计的东西:atomic、mutex、condition_variable等)。
异常安全。是的,只要三条规则得到解决。
向我解释一下你认为使用这种代码生成的利弊是什么?
不确定您是否需要一个lambda来这样做:
标准模式是使用make_X函数。见:make_pair()。
template<typename T>
class DynamicWrapper {
public:
DynamicWrapper() = default;
DynamicWrapper(const std::string_view name_in, T value) : name_{ name_in } {
this->pData_ = new T{ value };
}
~DynamicWrapper() {
if (nullptr != this->pData_) {
delete this->pData_;
this->pData_ = nullptr;
}
}
auto value() const { return *(this->pData_); }
auto ptr() const { return this->pData_; }
auto name() const { return this->name_; }
private:
T* pData_ = nullptr;
const std::string name_;
};
template<typename T>
DynamicWrapper<T> make_DynamicWrapper(std::string_view name_in, T&& value)
{
return DynamicWrapper<T>(std::move(name_in), std::forward<T>(value));
}
int main()
{
auto C = make_DynamicWrapper("Foo", 7);
}在无需使用智能指针的情况下,当涉及到在此上下文中定义的自包含对象中使用new和delete时,是否还需要注意到其他什么?
遵守三/五的规则。
使用这种结构的副作用是什么?
没什么真正的。
我可以或需要做些什么来改进这个代码片段,使它成为一个定义良好的代码基,不引入任何潜在的UB?
看起来不错。
使用这类实现的潜在利用是什么?
不知道那是什么意思。
如果采取了所有必要的预防措施消除任何代码气味..。
没有气味。
这种代码结构在任何一种生产代码中都是有用的工具吗?
当然是。
有一点要注意的是,lambda表达式只是编写函子的短手符号。因此,我们可以重写您的lambda,如下所示(它显示了编译器中的实际情况)。
template<typename T>
struct Lambda_986b701ab3be1e8cd3d0d2d875c96c7d
{
class DynamicWrapper {
public:
DynamicWrapper() = default;
DynamicWrapper(const std::string_view name_in, T value) : name_{ name_in } {
this->pData_ = new T{ value };
}
~DynamicWrapper() {
if (nullptr != this->pData_) {
delete this->pData_;
this->pData_ = nullptr;
}
}
// define copy constructor and assignment operator here
// wrt how you want this wrapper class to behave in order
// to preserve the rule of 3 or 5. Meaning, do you want
// to allow this class object to be copyable or not...
auto value() const { return *(this->pData_); }
auto ptr() const { return this->pData_; }
auto name() const { return this->name_; }
private:
T* pData_ = nullptr;
const std::string name_;
};
DynamicWrapper operator()(const std::string_view name, T value) const
{
return DynamicWrapper(name, value);
}
};
int main()
{
auto c = Lambda_986b701ab3be1e8cd3d0d2d875c96c7d<int>{}("Fun", 7);
}发布于 2021-02-06 13:11:56
首先,根本不清楚这是为了什么,这与std::pair<T, std::string>有什么不同。动机是什么?
这在C++20中是行不通的:
255676.cpp:5:32: error: non-local lambda expression cannot have a capture-default
5 | auto create_dynamic_wrapper = [&](const std::string_view name, T value) {
| ^我们似乎不需要默认的捕获,所以我们可以直接使用[] --但是为什么不直接编写一个函数呢?
实际上,我刚刚检查过,而且在C++17中也是不合法的,所以代码是无效的。
DynamicWrapper() = default;
这个默认构造函数是什么?我们从不用它。
DynamicWrapper(const std::string\_view name\_in, T value) : name\_{ name\_in } { this->pData\_ = new T{ value }; }
为什么要分配给pData,而不是像name那样简单地初始化它?另外,使name_in成为const参数迫使我们将其复制到name中,而不是能够移动它。我会写:
DynamicWrapper(std::string name, T value)
: pData_{new T{std::move(value)}},
name_{std::move(name)}
{
}~DynamicWrapper() { if (nullptr != this->pData\_) { delete this->pData\_; this->pData\_ = nullptr; } }
这里乱七八糟的。首先,我们可以抛弃所有只会降低代码可读性的this->垃圾。为什么我们要与空指针进行比较?如果pData_为null,那么就不需要进行测试了,因为delete无论如何都不会做任何事情。这是对pData_的死赋值,因为对象超出了作用域。所有这些都可以用一个简单得多的析构函数来代替:
~DynamicWrapper() {
delete pData_;
}然而,我认为我们应该重新考虑pData_的类型。正如g++ -Weffc++警告我们的那样,我们有一个指针数据成员,但是没有复制构造函数和复制赋值操作符,这使得使用它成为一个危险的类。
如果我们用智能指针替换pData_,那么编译器生成的(或编译器删除的)构造函数、析构函数和赋值只会做正确的事情(即零规则)。
下面是同一件事的一个简单版本:
#include <memory>
#include <string>
template<typename T>
auto create_dynamic_wrapper(std::string name, T value)
{
class DynamicWrapper {
public:
DynamicWrapper(std::string name, T value)
: value_{new T{std::move(value)}},
name_{std::move(name)}
{
}
auto value() const { return *value_; }
auto const& ptr() const { return value_; }
auto name() const { return name_; }
private:
const std::shared_ptr<T> value_;
const std::string name_;
};
return DynamicWrapper(std::move(name), std::move(value));
}不过,我还是不明白重点。
https://codereview.stackexchange.com/questions/255676
复制相似问题