C++ 是否有更好的方法使ostream操作员过载<&书信电报;?
假设您有以下代码:C++ 是否有更好的方法使ostream操作员过载<&书信电报;?,c++,operator-overloading,c++17,iostream,ostream,C++,Operator Overloading,C++17,Iostream,Ostream,假设您有以下代码: #include <iostream> template <typename T> class Example { public: Example() = default; Example(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { } friend std::ostream
#include <iostream>
template <typename T>
class Example
{
public:
Example() = default;
Example(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { }
friend std::ostream &operator<<(std::ostream &os, const Example &a)
{
return (os << a.first_ << " " << a.second_);
}
private:
T first_;
T second_;
};
int main()
{
Example example_(3.45, 24.6); // Example<double> till C++14
std::cout << example_ << "\n";
}
#包括
样板
课例
{
公众:
示例()=默认值;
示例(常数T和常数T和常数秒):第一个(第一个),第二个(第二个)
朋友STD::OsFase&运算符这是最明显的实现方法。它也是最有效的。使用它。
< P>你在这个问题中演示的方式是最基本的方法,在各种C++书籍中也有发现。个人来说,我不喜欢我的生产代码,主要是因为:
- 必须为
friend operator编写样板代码我相信这些评论已经很好地回答了您的问题。从纯性能的角度来看,可能没有“更好”的方法使过载。据我所知,这个问题有两个模糊点:
- 是否专门针对模板化类。
我假设答案是肯定的
- 是否有更好的方法来重载
ostream操作符,或者您认为有更好的选择吗?想想它在做什么,并询问您不需要的部分。有没有?有什么不同吗?看起来如何?试试看,看看它是否更快。您有什么性能问题e这里?外部I/O的成本通常比代码的成本高得多,如果不是这样,iostreams库也不会特别快。@EmanueleOggiano:“Emanuele Oggiano正在寻找一个规范的答案。”很难提供一个“规范的答案”当不清楚问题的实质是什么时。你所说的“重载运算符的方式”是什么意思?取决于你如何定义“更好”-如果没有这一点(根据定义),就不可能有规范的答案。没有要求一个运算符“它可能会因变量的数量不同而不同。“这种差异仍然存在,因为每个类都必须有一行调用这个Attach
函数。因此,这不是样板文件的一部分;唯一实际的样板文件是运算符”,最好是在它们的构造函数中,可以附加()要打印的成员变量。”由于您的Attach
函数现在是静态的,并且std::function
本身也是静态的,因此从类的构造函数调用Attach
只会导致大量使用相同的函数重写函数。@Nicolas,关于“样板文件”第二部分,我强调一致性和可读性。当从库代码进行打印时,可以确保打印的一致性。此外,只有一个Attach()
比使用friend
函数直观得多。继承ostream
提高了可读性,因为它声明此类具有打印功能。关于您关于覆盖的第二条评论,我认为您已经监督了代码中的延迟初始化。Attach()中的代码
将有效地在每个类(而不是每个类对象)中只运行一次。我删除它是因为我把编辑搞砸了:(在多级继承中,它需要虚拟继承,这对性能不好。不要为你不需要的东西付费。我们必须先过载这是一个相当危险的想法。现在,如果我们想打印成员名称呢?如果我们想打印一个类的索引,而不是打印另一个类的索引呢?如果我们想打印一些成员名称呢以十六进制表示的BER?这比您编写friend操作符virtual
的速度要快得多。不需要继承。已编辑。@n.“代词m。以1种方式打印成员已在上面的链接中演示。在最新版本中,我还添加了对std::vector
类容器的支持(还没有绘制地图,但应该很容易)。对于自定义打印,您可以随时定义其自定义运算符。如果您可以向下投票,您可以告诉我原因。我不反对学习。这不是一个合适的基准。1.您需要在循环中迭代基准以避免测量缓存效果。2.我不知道如何获得任何有用的精确打印微秒计数。使用optimiza启用tions后,每次执行大约需要1U到3U左右。3.如果您纠正了这些问题,那么仍然存在巨大的差异,但这仅仅是因为一些测试用例需要打印更长的字符串。如果您为所有示例*
变量提供相同的值,则没有明显的差异ymore.请参阅。具有原始值但循环且输出纳秒的基准位于顶部,在所有测试中具有相同值的基准位于底部。中间和右侧分别执行GCC和Clang生成的代码。@walnut-你说得对!考虑到注释,我修改了代码并给出了答案。
friend std::ostream &operator<<(std::ostream &os, const Example &a)
{
return (os << a.first_ << " " << a.second_);
}
// Add `is_iterable` trait as defined in https://stackoverflow.com/a/53967057/514235
template<typename Derived>
struct ostream
{
static std::function<std::ostream&(std::ostream&, const Derived&)> s_fOstream;
static auto& Output (std::ostream& os, const char value[]) { return os << value; }
static auto& Output (std::ostream& os, const std::string& value) { return os << value; }
template<typename T>
static
std::enable_if_t<is_iterable<T>::value, std::ostream&>
Output (std::ostream& os, const T& collection)
{
os << "{";
for(const auto& value : collection)
os << value << ", ";
return os << "}";
}
template<typename T>
static
std::enable_if_t<not is_iterable<T>::value, std::ostream&>
Output (std::ostream& os, const T& value) { return os << value; }
template<typename T, typename... Args>
static
void Attach (const T& separator, const char names[], const Args&... args)
{
static auto ExecuteOnlyOneTime = s_fOstream =
[&separator, names, args...] (std::ostream& os, const Derived& derived) -> std::ostream&
{
os << "(" << names << ") =" << separator << "(" << separator;
int unused[] = { (Output(os, (derived.*args)) << separator, 0) ... }; (void) unused;
return os << ")";
};
}
friend std::ostream& operator<< (std::ostream& os, const Derived& derived)
{
return s_fOstream(os, derived);
}
};
template<typename Derived>
std::function<std::ostream&(std::ostream&, const Derived&)> ostream<Derived>::s_fOstream;
class MyClass : public ostream<MyClass> {...};
// Use better displaying with `NAMED` macro
// Note that, content of `Attach()` will effectively execute only once per class
MyClass () { MyClass::Attach("\n----\n", &MyClass::x, &MyClass::y); }
#include"Util_ostream.hpp"
template<typename T>
class Example : public ostream<Example<T>> // .... change 1
{
public:
Example(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele)
{
Example::Attach(" ", &Example::first_, &Example::second_); // .... change 2
}
private:
T first_;
T second_;
};
t1 ~ 2.5-3.5 * t2
t2 ~ 1.02-1.2 * t3
template <typename T>
class Example
{
public:
Example(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { }
friend std::ostream &operator<<(std::ostream &os, const Example &a)
{
return (os << a.first_ << " " << a.second_);
}
private:
T first_;
T second_;
};
template <typename T>
class Example2
{
public:
Example2(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { }
void print(std::ostream &os) const
{
os << this->first_ << " " << this->second_;
return;
}
private:
T first_;
T second_;
};
template<typename T>
auto operator<<(std::ostream& os, const T& a) -> decltype(a.print(os), os)
{
a.print(os);
return os;
}
template <typename T>
class Example3
{
public:
Example3(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { }
void print(std::ostream &os) const
{
os << this->first_ << " " << this->second_;
return;
}
private:
T first_;
T second_;
};
// Note 1: If this function exists, the compiler makes it take precedence over auto... above
// If it does not exist, code compiles ok anyway and auto... above would be used
template <typename T>
std::ostream &operator<<(std::ostream &os, const Example3<T> &a)
{
a.print(os);
return os;
}
// Note 2: Explicit instantiation is not needed here.
//template std::ostream &operator<<(std::ostream &os, const Example3<double> &a);
//template std::ostream &operator<<(std::ostream &os, const Example3<int> &a);
#include <iostream>
#include <chrono>
int main()
{
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
const int nout = 10000;
Example example_(3.45, 24.6); // Example<double> till C++14
begin = std::chrono::steady_clock::now();
for (int i = 0 ; i < nout ; i++ )
std::cout << example_ << "\n";
end = std::chrono::steady_clock::now();
const double lapse1 = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
std::cout << "Time difference = " << lapse1 << "[us]" << std::endl;
Example2 example2a_(3.5, 2.6); // Example2<double> till C++14
begin = std::chrono::steady_clock::now();
for (int i = 0 ; i < nout ; i++ )
std::cout << example2a_ << "\n";
end = std::chrono::steady_clock::now();
const double lapse2a = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
std::cout << "Time difference = " << lapse2a << "[us]" << std::endl;
Example2 example2b_(3, 2); // Example2<double> till C++14
begin = std::chrono::steady_clock::now();
for (int i = 0 ; i < nout ; i++ )
std::cout << example2b_ << "\n";
end = std::chrono::steady_clock::now();
const double lapse2b = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
std::cout << "Time difference = " << lapse2b << "[us]" << std::endl;
Example3 example3a_(3.4, 2.5); // Example3<double> till C++14
begin = std::chrono::steady_clock::now();
for (int i = 0 ; i < nout ; i++ )
std::cout << example3a_ << "\n";
end = std::chrono::steady_clock::now();
const double lapse3a = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
std::cout << "Time difference = " << lapse3a << "[us]" << std::endl;
std::cout << "Time difference lapse1 = " << lapse1 << "[us]" << std::endl;
std::cout << "Time difference lapse2a = " << lapse2a << "[us]" << std::endl;
std::cout << "Time difference lapse2b = " << lapse2b << "[us]" << std::endl;
std::cout << "Time difference lapse3a = " << lapse3a << "[us]" << std::endl;
return 0;
}