返回其析构函数具有副作用的对象

时间:2019-02-14 10:04:38

标签: c++ c++14

我们有一个可帮助我们将数据发送到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 ++中的不良做法。

我已经尝试了一些关于返回右值引用的变体,但是要么尝试返回对临时变量或堆栈变量的引用,要么做一些同样愚蠢的事情。我在生产中发现的示例还有很多其他方面,我不确定该怎么做。

是否有针对这种模式的最佳实践的指针?还是我正在尝试做一些我不应该做的语法操作?

2 个答案:

答案 0 :(得分:2)

您必须在这些方法中返回对self的引用,而不是创建新对象。
也可以考虑实现move构造函数。

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);

答案 1 :(得分:0)

我将根据std::optional实现并提供一个move构造函数,以便在析构函数期间提供自动的“有效性”指示符。

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

完整示例:

#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 << "hello\n";
    }

    std::cout << "world\n";
}

预期输出:

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

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