返回对象的攻击子具有副作用

Return object whose destructor has side effects

本文关键字:副作用 攻击 对象 返回      更新时间:2023-10-16

我们有一个类,可以帮助我们将数据发送到telegraf/influxdb(即用于监视)。看起来很像这样:

class TelegrafSend {
  public:
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();
    // Destructor sends the object.
    ~TelegrafSend();
    // Exists in a couple variants.  Probably could have been a template.
    void AddTag(const std::string& tag_name, const std::string& tag_value);
    // Same.
    void AddField(const std::string& field_name, const int field_value);
};

要清楚,看起来像这样:

TelegrafSend TelegrafSend::AddField(const string& field_name, const int field_value) {
    fields_[field_name] = to_string(field_value);
    sent_ = false;
    return *this;
}

这很好:

TelegrafSend telegraf;
telegraf.AddTag("a_tag", "a_value");
telegraf.AddField(kTelegrafCount, 1);

并且由于它不在范围时,它将被发送,这是一个不错的行为,因为一个函数可以在执行时添加几个指标,并且从函数中的所有退出会导致对象发送。

现在我有一个聪明的主意:

class TelegrafSend {
  public:
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();
    // Destructor sends the object.
    ~TelegrafSend();
    // Exists in a couple variants.  Probably could have been a template.
    TelegrafSend AddTag(const std::string& tag_name, const std::string& tag_value);
    // Same.
    TelegrafSend AddField(const std::string& field_name, const int field_value);
};

,所以我可以写

TelegrafSend telegraf.AddTag("a_tag", "a_value").AddField(kTelegrafCount, 1);

这里的问题是我正在创建临时工,因此,尽管这最终起作用,但每个退货都会创建一个临时性,该临时性被摧毁并因此发送到Telegraf。对于InfluxDB而言,这确实效率低下,甚至没有谈论C 中的不良练习。

我尝试过一些关于返回rvalue参考的变体,但是我要么尝试返回对临时变量或堆栈变量的参考,要么做一些同样愚蠢的事情。我在生产中发现的示例做了很多其他事情,以至于我不确定该怎么做。

有没有指导这种模式的最佳实践?还是我想做一些我不应该做的句法?

您必须在这些方法中返回对自我的引用,而不是创建新对象。
实施移动构造函数也可能考虑。

class TelegrafSend {
  public:
    TelegrafSend();
    ~TelegrafSend();
    TelegrafSend(const TelegrafSend&) = delete;
    TelegrafSend& operator = (const TelegrafSend&) = delete;
    TelegrafSend(TelegrafSend&&); // possibly = delete;
    TelegrafSend& operator = (TelegrafSend&&); // possibly = delete;
    // Exists in a couple variants.  Probably could have been a template.
    TelegrafSend& AddTag(const std::string& tag_name, const std::string& tag_value)
    {
        /*..*/
        return *this;
    }
    // Same.
    TelegrafSend& AddField(const std::string& field_name, const int field_value)
    {
        /*..*/
        return *this;
    }
};

然后您可以使用:

TelegrafSend{}.AddTag("a_tag", "a_value").AddField(kTelegrafCount, 1);

我将以std::optional的形式实现并提供一个移动构造函数,以便在破坏者期间提供自动"有效性"指标。

请注意,从可选的移动不会清空它,它只会将内容移出,因此您必须重置可选的。

完成示例:

#include <optional>
#include <string>
#include <iostream>
struct TelegrafSend 
{
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();
    TelegrafSend(TelegrafSend&& other);
    TelegrafSend(TelegrafSend const& ) = delete;
    TelegrafSend& operator=(TelegrafSend const& ) = delete;
    TelegrafSend& operator=(TelegrafSend && ) = delete;
    // Destructor sends the object.
    ~TelegrafSend();
    TelegrafSend& AddTag(const std::string& tag_name, const std::string& tag_value);
    TelegrafSend& AddField(const std::string& field_name, const int field_value);
private:
    struct Impl
    {
        std::string narrative;
        void more(std::string const& s)
        {
            if (!narrative.empty())
                narrative += 'n';
            narrative += s;
        }
        void emit()
        {
            if (narrative.empty())
                std::cout << "{empty}n";
            else
                std::cout << narrative << 'n';
        }
    };
    std::optional<Impl> impl_;
};
TelegrafSend::TelegrafSend() 
: impl_(Impl())
{
}
TelegrafSend::TelegrafSend(TelegrafSend&& other)
: impl_(std::move(other.impl_))
{
    other.impl_.reset();
}
TelegrafSend::~TelegrafSend() 
{
    if(impl_.has_value())
        impl_->emit();
}
TelegrafSend& TelegrafSend::AddTag(const std::string& tag_name, const std::string& tag_value)
{
    auto s = "Tag : " + tag_name + " : " + tag_value;
    impl_->more(s);
    return *this;
}
TelegrafSend& TelegrafSend::AddField(const std::string& field_name, const int field_value)
{
    auto s = "Field : " + field_name + " : " + std::to_string(field_value);
    impl_->more(s);
    return *this;
}

auto test(TelegrafSend ts = {}) -> TelegrafSend
{
    ts.AddTag("foo", "bar").AddField("baz", 6);
    return ts;
}
int main()
{
    {
        test(), std::cout << "hellon";
    }
    std::cout << "worldn";
}

预期输出:

hello
Tag : foo : bar
Field : baz : 6
world

https://coliru.stacked-crooked.com/a/755d3d161b9d48b3