c++ 如何最好的DRY与一些ad-hoc类示例?

uqxowvwt  于 9个月前  发布在  其他
关注(0)|答案(4)|浏览(89)

有时在我的代码中,我发现自己想要定义同一个类的多个示例,定义中的差异相对于其整体长度来说很小,并且共享一些公共指示符。而且-我努力重构代码,以便它既紧凑又易于使用。
示例:我从以下内容开始:

auto blue_foo = std::make_unique<float[]>(n);
auto blue_bar = std::make_unique<float[]>(m);
auto blue_baz = std::make_unique<float[]>(k);

字符串
这是一种讨厌的:重复的命令和名称前缀.:-(
我可以把这些变量放在一个名为blue的结构中,这样我就可以写

do_stuff_with(blue.foo, blue.baz, 123);


而不是

do_stuff_with(blue_foo, blue_baz, 123);


但是-然后我需要更多的重复:

struct {
    std::unique_ptr<float[]> foo, bar, baz;
} blue = {
    std::make_unique<float[]>(n),
    std::make_unique<float[]>(m),
    std::make_unique<float[]>(k),
};


太浪费了!我至少想写这样的东西:

struct { auto foo, bar, baz; } foo = {
    std::make_unique<float[]>(n),
    std::make_unique<float[]>(m),
    std::make_unique<float[]>(k),
};


但这不可能
另外,我不想重复make_unique调用。我可以写一个lambda,我想:

auto make_data = [](std::size_t size) { return std::make_unique<float[]>(size); }

struct { auto foo, bar, baz; } blue = { make_data(n), make_data(m), make_data(k) };


但是lambda需要写很多东西,它并不能保存那么多。即使这样也有一些重复。
什么是使我的初始代码段紧凑且可读的惯用方法?

备注:在这个例子中,代码片段并没有重复很多次,只有一次。如果我重复使用foo,bar和baz的组合,我会写一个类来保存它们。

3wabscal

3wabscal1#

我能想到的最短(但不一定是最容易理解的)版本是使用一个可变参数模板立即调用lambda:

#include <memory>

int main() {
    auto blue = [](auto... ns) {
        struct { 
            std::unique_ptr<float[]> foo, bar, baz; 
        } ret { std::make_unique<float[]>(ns)... };
        return ret;
    }(1, 2, 3);
}

字符串
https://godbolt.org/z/9c8cvc8oW
它没有任何重复,但我不确定你会称之为惯用法。
如果您可以使用new而不是std::make_unique并重复new float[x]三次,则可以简化此操作并提高可读性:

#include <memory>

int main() {
    struct { 
        std::unique_ptr<float[]> 
            foo{new float[1]}, 
            bar{new float[2]}, 
            baz{new float[3]}; 
    } blue;
}

tkqqtvp1

tkqqtvp12#

在您的特定示例中,所有成员都具有相同的类型,这使得std::array成为一个可行的选项:

using array_type = float[];
auto [foo, bar, baz] = std::array{
    std::make_unique<array_type>(n),
    std::make_unique<array_type>(m),
    std::make_unique<array_type>(k),
};

字符串
这消除了大部分重复,但std::make_unique仍然会被调用三次。对于较小的示例,您可以如下解决此问题:

constexpr auto make = [](std::size_t size) { return std::make_unique<float[]>(size); };
auto [foo, bar, baz] = std::array{make(n), make(m), make(k)};


对于更大的示例,即使多次调用make也有问题,您可以编写如下代码:

// reusable code
template <typename T, std::size_t N, typename F>
auto transform(std::array<T, N> data, F f)
  -> std::array<decltype(f(data[0])), N>
{
    return std::apply([&f](auto&... x) -> decltype(transform<T, N, F>(data, f)) {
        return { f(x)... };
    }, data);
}

// one-time code
constexpr  auto make = [](std::size_t size) { return std::make_unique<float[]>(size); };
auto [foo, bar, baz] = transform(std::array{n, m, k}, make);


如果不使用std::array,而是使用T(&data)[N]参数(类似于std::to_array),则可以获得语法transform({n, m, k}, make);

wnavrhmk

wnavrhmk3#

你可以创建一个helper函数,返回unique_ptrstd::tuple,然后使用structure bindings。大致思路如下:

auto make(float n, float m, float k)
{ 
 return std::tuple{std::make_unique<float[]>(n), std::make_unique<float[]>(m), std::make_unique<float[]>(k)};
} 
    
auto [blue_A, blue_B, blue_C] = make(n,m, k);

字符串

ymdaylpp

ymdaylpp4#

@NathanOliver提出了一个有趣的建议,使用字典/std::map。但这仍然让我重复了一堆代码。如果我们有一个let,它有一个合适的Transformer,将key-ctor-arguments对转换为key-constructed-values对,并且如果我们可以示例化它,而不存在任何模板二义性问题,那么我们可以这样写:

dict<std::make_unique<float[]>> blue { 
    { "foo", n }, 
    { "bar", m },
    { "baz", k }
};

do_stuff_with(blue["foo"], blue["baz"], 123);

字符串
优点:

  • 没有任何重复;具体地说,我们只需要一个模板化的dict定义,用于所有程序中的所有此类情况。
  • 无人为前缀
  • 可以将所有blue一起传递

损害:

  • 还没有真正实施这一点,以建立它的工作...
  • 更多的精神负荷读者的这段代码
  • 定制类,不在标准库中
  • 使用它的代码中的性能开销

相关问题