首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用lambda一般地在T类型的对象周围创建任意类包装器

使用lambda一般地在T类型的对象周围创建任意类包装器
EN

Code Review用户
提问于 2021-02-06 01:51:59
回答 2查看 103关注 0票数 -1

在尝试一些C++'s语言特性时,我通过使用lambda成功地设计了一个无法调用的函数调用,它一般地创建一个任意类包装器,该类包装器返回类对象,而不必在它自己的独立翻译单元中直接实现该类。包装类本身仍然包含在一个翻译单元中,因为它是在lambda的范围内声明和定义的。这个lambda然后通过使用模板类型推断返回这个类型。

在我的例子中,我在类型T对象周围创建了一个包装类,因为它存储了指向该对象的指针及其构造函数和析构函数,分别对该指针调用了new和delete。成员是私有的,只能通过类的公共接口访问,并且它们被声明为不可修改的const,这意味着调用这些方法的使用不会改变生成的类对象的状态。为了更好地说明这一点,下面是我的源代码:

ome.h

代码语言:javascript
复制
#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);
}; 

驱动程序

代码语言:javascript
复制
#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;
}

当我在我的机器上运行这个程序时,我得到以下输出:

输出

代码语言:javascript
复制
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

第二行将因使用动态堆内存分配而发生变化。第四行因命名约定、名称损坏、符号生成等原因而有所不同,取决于编译器、操作系统和其他因素。

现在谈谈我的问题和关切..。

  • 我不知道这种类型的代码结构/生成是否有特定的惯用名称。如果是这样的话,这个成语会叫什么?
  • 这被认为是一个定义明确的计划吗?
  • 使用这种结构的含义是什么:
    • 这会引发任何形式的UB吗?
    • 这是否有可能引入内存泄漏、无效的或悬空的指针或引用?
    • 这会被认为是线程和异常安全吗?

  • 向我解释一下你认为使用这种代码生成的利弊是什么?
  • 在不必使用smart-pointers的情况下,当涉及到在这个上下文中定义的自包含对象中使用new和delete时,我还需要知道其他什么吗?
  • 使用这种结构的副作用是什么?
  • 我可以或需要做些什么来改进这个代码片段,使它成为一个定义良好的代码基,不引入任何潜在的UB?
  • 使用这类实现的潜在利用是什么?
  • 如果采取了所有必要的预防措施消除任何代码气味..。这种代码结构在任何一种生产代码中都是有用的工具吗?

-Note给读者-

关于这个代码结构,我觉得有些地方很有趣。lambda本身在内部和本地声明和定义一个名为DynamicWrapper<T>的类对象,其中定义和声明该类在此lambda的范围内,并通过使用auto type deduction返回该类型的实例化类实例。

然后,在调用和调用此lambda的某个转换单元中,通过使用auto说明符,返回到用户声明的auto variable中的命名对象实际上是一个DynamicWrapper<T>实例,即使在被调用的lambda之外不存在这样的类声明或定义。在总体设计模式和实现方面,我发现这是一组非常有趣的属性和行为。

EN

回答 2

Code Review用户

发布于 2021-02-06 02:41:48

我不知道这种类型的代码结构/生成是否有特定的惯用名称。如果是这样的话,这个成语会叫什么?

我自己也不确定。

这被认为是一个定义明确的计划吗?

在我看来不错。

使用这种结构意味着什么:

  • 这会引发任何形式的UB吗?

看起来身材很好。

  • 这是否有可能引入内存泄漏、无效的或悬空的指针或引用?

在一般情况下没有。

在这种特殊情况下:您没有实现3/5的规则,而是管理类内动态创建的资源。因此,绝对有可能出现资源处理不当的情况。

这会被认为是线程和异常安全吗?

没有任何东西是线程安全的,除非您显式地这样做(除了专门为线程设计的东西:atomicmutexcondition_variable等)。

异常安全。是的,只要三条规则得到解决。

向我解释一下你认为使用这种代码生成的利弊是什么?

不确定您是否需要一个lambda来这样做:

标准模式是使用make_X函数。见:make_pair()

代码语言:javascript
复制
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,如下所示(它显示了编译器中的实际情况)。

代码语言:javascript
复制
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);
}
票数 2
EN

Code Review用户

发布于 2021-02-06 13:11:56

首先,根本不清楚这是为了什么,这与std::pair<T, std::string>有什么不同。动机是什么?

这在C++20中是行不通的:

代码语言:javascript
复制
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中,而不是能够移动它。我会写:

代码语言:javascript
复制
    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_的死赋值,因为对象超出了作用域。所有这些都可以用一个简单得多的析构函数来代替:

代码语言:javascript
复制
    ~DynamicWrapper() {
        delete pData_;
    }

然而,我认为我们应该重新考虑pData_的类型。正如g++ -Weffc++警告我们的那样,我们有一个指针数据成员,但是没有复制构造函数和复制赋值操作符,这使得使用它成为一个危险的类。

如果我们用智能指针替换pData_,那么编译器生成的(或编译器删除的)构造函数、析构函数和赋值只会做正确的事情(即零规则)。

下面是同一件事的一个简单版本:

代码语言:javascript
复制
#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));
}

不过,我还是不明白重点。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/255676

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档