首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++11中的任一单曲

C++11中的任一单曲
EN

Code Review用户
提问于 2020-12-01 23:52:57
回答 1查看 494关注 0票数 1

我将C++11与https://github.com/SmingHub/Sming结合使用。它不支持std库或异常。我想使用Either monad返回函数的错误或结果。我使用类层次结构实现了它。我不是一个本地的C++开发人员,所以我很乐意对代码进行审查。

代码语言:javascript
复制
#pragma once

template <class L, class R> 
class Either
{
    virtual bool isLeft();
    virtual bool isRight();

    virtual bool matchLeft(L& x);
    virtual bool matchRight(R& x);
};

template <class L, class R> 
class Left : public Either<L, R> {
public:
    L left;

    Left(L left): left(left) {}
    constexpr bool isLeft() { return true; }
    constexpr bool isRight() { return false; }

    bool matchLeft(L& x)
    {
        x = left;
        return true;
    }
    bool matchRight(R& x) { return false; }
};

template <class L, class R> 
class Right : public Either<L, R> {
public:
    R right;

    Right(R right): right(right) {}
    constexpr bool isLeft() { return false; }
    constexpr bool isRight() { return true; }

    bool matchLeft(L& x) { return false; }

    bool matchRight(R& x) {
        x = right;
        return true;
    }
};
EN

回答 1

Code Review用户

回答已采纳

发布于 2020-12-02 22:42:47

这个类在概念上有缺陷,因为从它提取值的唯一方法是(复制)赋值。这使得它没有我们想要的那么有用:

代码语言:javascript
复制
Either<err_type, res_type> *result = some_function(); // notice the pointer; more on this later
if(result->isLeft()) {
    some_type error; // what if there's no obvious and cheap way to create an "empty" object of this type?
                     // why do I have to state the type again?
    result->matchLeft(error); // what if there is no copy assignment operator= on some_type (e.g. it holds some exclusive resource)?
                              // what if the copy assignment is expensive (e.g. it's a container)?
                              // why is this not an expression?
    std::cerr << error << "\n"; // or however you're doing output
} else {
    other_type value;
    result->matchRight(value);
    std::cout << value << "\n";
}
delete result; // argh!

我们可以从标准库的书(std::get_if)中提取一个页面,并将matchLeftmatchRight变成getIfLeftgetIfRight,它们返回可空的指针。

代码语言:javascript
复制
template<class L, class R> 
struct Either // note that all of these members are private; you either need to add public: or replace class with struct
{
    // without const, these can't be called on const Eithers
    virtual bool isLeft() const = 0; // can actually be implemented in terms of getIfLeft, so doesn't really need to be virtual at all
    virtual bool isRight() const = 0; // otherwise, I think you need to make these all *pure* virtual, or else you'll get linker errors

    virtual L *getIfLeft() = 0;
    virtual R *getIfRight() = 0;
    // we'd also want const Either -> pointer to const versions, because these functions currently can't be called on const Eithers

    virtual ~Either() = default; // very, VERY severe omission! deleting an Either through a pointer without this is UB!
};

*result->getIfLeft()现在是L类型的lvlaue。调用方不再需要构造虚拟对象或重新声明类型,L不再需要复制分配,我们需要更少的行。请注意,我们基本上刚刚重新发明了dynamic_cast

代码语言:javascript
复制
Either<some_type, other_type> *result = some_function();

// now shorter, more to the point
if(result->isLeft()) std::cerr << *result->getIfLeft() << "\n";
else std::cout << *result->getIfRight() << "\n";
// also acceptable
if(auto error = result->getIfLeft()) std::cerr << *error << "\n";
else if(auto value = result->getIfRight()) std::cout << *value << "\n";

delete result;

另一个问题是首先使用虚拟继承。这意味着使用该类的唯一方法是通过指针,可能是动态分配,这很容易出错(我一开始忘记在这个答案中编写deletes;您忘了让析构函数virtual;上面的some_function可能必须包含一个new,所以我们可能应该对它进行delete,但是some_function可能返回一个指针,指向它重用的某个预先分配的内存,或者它可能做了其他事情,所以很难自动知道如何处理内存……)。一个更直接的方法是经典的(老的C)“标记的联合”。

std::variant是一个通用标记的联合,但它在C++11中不存在,而且您甚至说您没有可用的STL。实现缩减版本涉及一个union,这是一个有趣的正确处理,但关键是,最终结果就像“只是一个值”,而不需要用户代码知道如何管理内存。

代码语言:javascript
复制
// you say you don't have the std namespace available
// if you really don't have std::move or std::forward, just write them yourself
enum class either_side { left, right };
struct left_tag_t {}; struct right_tag_t {};
template<class L, class R>
class either {
    // self explanatory
    either_side tag;
    union {
        L left;
        R right;
    };

    public:
    // "in-place" constructors: according to the tag, initialize with whatever arguments
    template<class... U>
    either(left_tag_t, U&&... args) : tag(either_side::left), left(std::forward<U>(args)...) { }
    template<class... U>
    either(right_tag_t, U&&... args) : tag(either_side::right), right(std::forward<U>(args)...) { }
    // may want std::initializer_list "forwarding" constructors, too

    // initialize by type
    either(L x) : either(left_tag_t(), std::move(x)) { }
    either(R x) : either(right_tag_t(), std::move(x)) { }

    // sometimes we don't know which variant member to initialize immediately
    // so we leave the union uninitialized and then placement-new into it
    either(either const &other) : tag(other.tag) {
       if(tag == either_side::left) new(&left) L(other.left);
       else new(&right) R(other.right);
    }
    either(either &&other) : tag(other.tag) {
       if(tag == either_side::left) new(&left) L(std::move(other.left));
       else new(&right) R(std::move(other.right));
    }
    // and when we need to destroy the object, we must explicitly call the destructor
    either &operator=(either const &other) {
       if(tag == either_side::left && other.tag == either_side::left) left = other.left;
       else if(tag == either_side::right && other.tag == either_side::right) right = other.right;
       else if(tag == either_side::left && other.tag == either_side::right) {
           left.~L();
           // NOT exception safe! if this constructor were to throw and ~either to be called during the unwinding
           // then ~either would see the tag set but neither object constructed
           // and would try to destroy it a second time
           // ...but you say we don't have exceptions, so I suppose we can be sloppy (the standard library's solution is to add a third tag meaning "empty because of exception" to std::variant)
           new(&right) R(other.right);
       } else {
           right.~R();
           new(&left) L(other.left);
       }
       tag = other.tag;
    }
    either &operator=(either &&other) {
       if(tag == either_side::left && other.tag == either_side::left) left = std::move(other.left);
       else if(tag == either_side::right && other.tag == either_side::right) right = std::move(other.right);
       else if(tag == either_side::left && other.tag == either_side::right) {
           left.~L();
           new(&right) R(std::move(other.right));
       } else {
           right.~R();
           new(&left) L(std::move(other.left));
       }
       tag = other.tag;
    }
    ~either() {
        if(tag == either_side::left) left.~L();
        else right.~R();
    }

    L *get_if_left() { return tag == either_side::left ? &left : nullptr; }
    L const *get_if_left() const { return tag == either_side::left ? &left : nullptr; }
    R *get_if_right() { return tag == either_side::right ? &right : nullptr; }
    R const *get_if_right() const { return tag == either_side::right ? &right : nullptr; }
    bool is_left() const { return bool(get_if_left()); }
    bool is_right() const { return bool(get_if_right()); }
};
// hopefully, all that's correct (emphasis on the hopefully)

塔-达!

代码语言:javascript
复制
// all uses of std:: definitions are for demonstrative purposes
struct user_session {
    std::string user_name;
    long user_id;
    long session_id;
    std::fstream data_file; // or whatever you're using
    user_session(std::string user_name, long user_id, long session_id, std::fstream data_file)
    : user_name(std::move(user_name)), user_id(user_id), session_id(session_id), data_file(std::move(data_file))
    { }
};

either<std::string, user_session> acquire_session(long user_id) {
    std::stringstream builder;
    builder << "User" << user_id;
    std::string name = builder.str();
    if(std::rand() & 1) return {left_tag_t(), "Computer says no"};
    try {
        std::fstream data(name);
        return {right_tag_t(), std::move(name), user_id, std::rand(), std::move(data)};
    } catch(...) {
        return {left_tag_t(), "IO error"};
    }
    // no wacky allocations for either
}
int main() {
    auto result = acquire_session(std::rand());
    if(auto *error = result.get_if_left()) {
        std::cerr << *error << "\n";
        return EXIT_FAILURE;
    }
    std::cout << result.get_if_right()->user_name << "\n";
    // either is correctly destroyed automatically
}
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

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

复制
相关文章

相似问题

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