我将C++11与https://github.com/SmingHub/Sming结合使用。它不支持std库或异常。我想使用Either monad返回函数的错误或结果。我使用类层次结构实现了它。我不是一个本地的C++开发人员,所以我很乐意对代码进行审查。
#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;
}
};发布于 2020-12-02 22:42:47
这个类在概念上有缺陷,因为从它提取值的唯一方法是(复制)赋值。这使得它没有我们想要的那么有用:
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)中提取一个页面,并将matchLeft和matchRight变成getIfLeft和getIfRight,它们返回可空的指针。
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。
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,这是一个有趣的正确处理,但关键是,最终结果就像“只是一个值”,而不需要用户代码知道如何管理内存。
// 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)塔-达!
// 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
}https://codereview.stackexchange.com/questions/252909
复制相似问题