C ++:将类传递给vararg函数

我正在尝试创建一个类似于MS CString的类(也就是说,它将它传递给printf,它就像一个指向C字符串的指针,没有像“.c_str()”这样的额外丑陋的黑魔法。

这是这个类的第一个实现,只是工作,并没有提供任何有用的东西:

#include  #include  class CString { protected: struct CStringInfo { size_t Length; size_t MaxLength; }; public: CString() { Buffer = NULL; Assign(NULL); } CString(const char* chv) { Buffer = NULL; Assign(chv, 0); } ~CString() { if(Buffer) delete[] Buffer; Buffer = NULL; } size_t GetLength() { if(!Buffer) Alloc(1); return GetInfo()->Length; } size_t Resize(size_t size) { Alloc(size + 1); // + 0x00 Buffer[size] = 0; return size; } bool Assign(const char* value, size_t size = 0) { size_t strl = ((size) ? size : strlen(value)); if(!value || !(strl = strlen(value))) { if(!Buffer) Alloc(1); return false; } Alloc(strl + 1); memcpy(Buffer, value, strl); Buffer[strl] = 0; return true; } CString& operator = (const char* what) { Assign(what); return (*this); } CString& operator = (CString& string) { Assign(string.Buffer); return (*this); } operator const char* () { return Buffer; } protected: char* Buffer; void Alloc(size_t size) { if(!size) size = 1; char* nb = new char[size + sizeof(CStringInfo)]; char* nbb = nb + sizeof(CStringInfo); size_t cl = size - 1; if(Buffer) { if(cl > GetInfo()->Length) cl = GetInfo()->Length; if(cl) memcpy(nbb, Buffer, cl - 1); nbb[cl] = 0; *(CStringInfo*)(nb) = *(CStringInfo*)(Buffer); delete[] (Buffer - sizeof(CStringInfo)); } Buffer = nb; GetInfo()->MaxLength = size; GetInfo()->Length = cl; } void Free() { if(Buffer) { delete[] (Buffer - sizeof(CStringInfo)); } } CStringInfo* GetInfo() { return (CStringInfo*)(this->Buffer - sizeof(CStringInfo)); } }; 

我测试的代码是:

 #include  #include "CString.hpp" CString global_str = "global string!"; int main(int argc, char* argv[]) { CString str = "string"; printf("Test: %s, %s\n", str, global_str); return 0; } 

如果我在类中没有析构函数,那么我可以将它传递给printf,它将像它应该的那样工作(作为C字符串)。 但是当我添加析构函数时,GCC会产生以下错误:

 error: cannot pass objects of non-trivially-copyable type 'class CString' through '...' 

除了之前版本的GCC还会发出警告+ ud2操作码。

所以…问题:我是否可以在GCC中实际进行以下构造工作,或者是否有任何方法(可能不涉及C varargs)使用与上述代码相同的内容?

您可以使用强制转换触发转换运算符:

 printf("Test: %s, %s\n", static_cast(str), static_cast(global_str)); 

但是,我不知道你是否会遇到任何问题,避免使用C ++代码中的varargs可能是最好的。

如何使用类型安全的printf (Credit:Wikipedia):

 void printf(const char *s) { while (*s) { if (*s == '%') { if (*(s + 1) == '%') { ++s; } else { throw std::runtime_error("invalid format string: missing arguments"); } } std::cout << *s++; } } template void printf(const char *s, T value, Args... args) { while (*s) { if (*s == '%') { if (*(s + 1) == '%') { ++s; } else { std::cout << value; printf(s + 1, args...); // call even when *s == 0 to detect extra arguments return; } } std::cout << *s++; } throw std::logic_error("extra arguments provided to printf"); } 

我不认为libstdc ++支持std::runtime_errorstd::logic_error

你几乎需要直接调用一个成员函数( foo.c_str() )或者像一个foo.c_str()(char *)foo )。

否则,它取决于编译器。 在C ++ 03中,行为未定义(§5.2.2/ 7):

当给定参数没有参数时,参数的传递方式是接收函数可以通过调用va_arg(18.7)来获取参数的值。 在参数表达式上执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。 在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。 如果参数具有非POD类类型(第9节),则行为未定义。

……但在(C ++ 11,§5.2.2/ 7)中,它有条件地支持:

当给定参数没有参数时,参数的传递方式是接收左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换是在参数表达式上执行的。 具有(可能是cv-qualified)类型std :: nullptr_t的参数将转换为void *(4.10)类型。 在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。 传递具有非平凡复制构造函数,非平凡移动构造函数或非平凡析构函数的类类型(第9章)的潜在评估参数,没有相应的参数,通过实现定义的语义有条件地支持。

“有条件地支持实现定义的语义”为实现提供了一个开放,以便通过适当的文档来支持它,但它仍然接近于未定义的行为。

如果我要这样做,我想我会使用可变参数模板设置某种中介。 这样,当你传递std::string类型的参数时,你会提供一个重载(例如)自动将foo.c_str()传递给printf 。 它(可能)更多的代码,但至少它实际上是有效的。 就个人而言,我会避免整个过程,因为它只是比它的价值更麻烦。

你试图解决这个问题(丑陋,说实话)字符串类? 为什么不使用smth呢? (比如std::string ) – 在开始编写你自己的超级优化字符串之前请三思而后行……

关于你的问题:你真的很幸运你的示例代码! 你知道椭圆在C中是如何工作的(在机器代码中)以及为什么不允许它通过它传递非平凡的类型? 简而言之: printf()只是查看一个格式字符串,如果它看到’%s’,它假设下一个参数是一个char*就是全部! 所以,如果你传递任何其他东西(比如charshort等) – 它将是UB! (并且如果sizeof()大于预期,很快就会很快出现分段错误……这就是为什么省略号在C ++中是一个不好的做法 !它们完全是类型不安全的!

如果您使用的是C ++, 请不要使用 C API! 有很多C ++库设计用于格式化输出(如boost :: format),它们是类型安全的! C ++ 11打开了类似printffunction的大门,但有类型安全保证! 只是阅读关于可变参数模板的“经典”示例…并且只有在阅读之后,尝试实现自己的字符串:)

您不能通过varargs传递对象,只能指向对象。 但是,您可以使用printf的(variadic)基于模板的实现,例如C ++格式提供的实现:

 #include "format.h" #include "CString.hpp" CString global_str = "global string!"; std::ostream &operator<<(std::ostream &os, const CString &s) { return os << static_cast(s); } int main() { CString str = "string"; fmt::printf("Test: %s, %s\n", str, global_str); } 

这将打印“Test:string,global string!” 只要CString正确实现。

与Jesse Good的实现不同,它支持标准的printf格式说明符 。

免责声明 :我是这个图书馆的作者