如何使一个类型易于通过引用进行比较

How to make a type that is easily compared by reference

本文关键字:引用 比较 易于通 何使一 类型      更新时间:2023-10-16

如何创建一个通过引用进行比较的类型,但强制它不能在函数中创建(防止引用堆栈/删除对象)

我想出了下面的Error类型,并为此感到自豪,直到我意识到你可以在函数中做"return Error::New(…)"。问题出在h()函数。

#include <stdio.h>
#include <string.h>
#include <string>
using namespace std;
class Error {
    std::string _str;
    const Error &from;
    Error();
    Error(const char *s)
        : _str(s), from(*this)
    {
    }
public:
    Error(const Error &err)
        : from(err.from)
    {
    }
    static Error New(const char *s) {
        return Error(s);
    }
    bool operator== (const Error &rhs) const {
        return (&from == &rhs.from);
    }
    bool operator!= (const Error &rhs) const {
        return (&from != &rhs.from);
    }
    std::string ToString() {
        return from._str;
    }
public:
    static const Error None;
};
const Error Error::None("none");
// user errors
auto ErrConnect = Error::New("failed to connect");
auto ErrWrite = Error::New("invalid write");
Error f() {
    return ErrConnect;
}
Error g() {
    return Error::None;
}
Error h() {
    return Error::New("test");
}
int main()
{
    printf("ErrConnect == ErrConnect : %dn", ErrConnect == ErrConnect);
    printf("ErrConnect == ErrWrite : %dn", ErrConnect == ErrWrite);
    printf("f() == ErrConnect : %dn", f() == ErrConnect);
    printf("f() == ErrWrite : %dn", f() == ErrWrite);
    printf("f() != ErrConnect : %dn", f() != ErrConnect);
    printf("f() != ErrWrite : %dn", f() != ErrWrite);
    printf("f() == Error::None : %dn", f() == Error::None);
    printf("f() != Error::None : %dn", f() != Error::None);
    printf("g() == Error::None : %dn", g() == Error::None);
    printf("g() != Error::None : %dn", g() != Error::None);
    printf("f().ToString() : %sn", f().ToString().c_str());
    printf("ErrConnect.ToString() : %sn", ErrConnect.ToString().c_str());
    auto err = f();
    auto err2 = err;
    auto err3 = err2;
    printf("err3 == ErrConnect : %dn", err3 == ErrConnect);
    auto err4 = h();
    printf("err4 from h() : %sn", err4.ToString().c_str());
}

允许在全局范围内创建某些内容,而不是在函数中创建。

您所做的任何事情都不需要跟踪原始Error&。相反,您希望创建一个错误来创建一个唯一的令牌,所有的Error复制或从它移动,以携带这个令牌。该令牌不是必需的Error& this,但是该令牌的生存期需要延长到从原始复制的所有Error的生存期。

一种方法是使用类型标记(可能是宏辅助的)来生成错误。注意,可以将令牌安排为唯一的,并且不需要在每个错误的基础上进行额外的工作。

例如:

struct ErrorToken {
  virtual std::string Description() const = 0;
  ~ErrorToken() {}
};
template<typename T>
struct ErrorTokenImpl:ErrorToken {
  virtual std::string Description() const /* final override if C++11 */ {
    return T::desc();
  }
};
class Error {
  ErrorToken* token;
  template<typename T>
  static ErrorToken* get_token() {
    static std::unique_ptr<ErrorToken> retval( new ErrorTokenImpl<T>() );
    return retval.get();
  }
public:
  template<typename T>
  Error(): token( get_token<T>() );
  bool operator==(Error const& o) const { return token == o.token; } // etc
  std::string GetDescription() const {
    return token->Description();
  }
};
#define MAKE_ERROR(Y, X) Error< struct Y { static const char* desc() { return X; }; } >()
const Error ErrConnect = MAKE_ERROR(Connection, "failed to connect");

现在,任何人都可以在任何上下文中创建错误,但是每个错误的创建都有一个标记和一个字符串,并且创建的token将持续到静态对象清理时间。

我发现实现这一点的最佳方法是使用静态std::atomic计数器(具体来说,_uint_fast64_t似乎最好),其中为error (const char *s)构造函数和std::atomic::fetch_add()中的每种错误类型创建id来获取/增加它:

http://en.cppreference.com/w/cpp/atomic/atomic

唯一的缺点是这些只存在于c++ 11 (Visual Studio 2012;Linux应该不是问题(除了旧的发行版)。

我最终使用UUID:

#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
using namespace std;
class Error {
    std::string msg;
    boost::uuids::uuid tag;
public:
    Error(const char *s)
        : msg(s), tag(boost::uuids::random_generator()())
    {
    }
    Error(const Error &rhs)
        : msg(rhs.msg), tag(rhs.tag)
    {
    }
    Error(Error &&rhs)
        : msg(std::move(rhs.msg)), tag(rhs.tag)
    {
    }
    Error &operator=(const Error &rhs) {
        msg = rhs.msg;
        tag = rhs.tag;
        return *this;
    }
    bool operator==(const Error &rhs) const {
        return (tag == rhs.tag);
    }
    bool operator!=(const Error &rhs) const {
        return (tag != rhs.tag);
    }
    std::string ToString() {
        return msg;
    }
public:
    static const Error None;
};
const Error Error::None("none");
// user errors
auto ErrConnect = Error("failed to connect");
auto ErrWrite = Error("invalid write");