将类传递给变量函数

C++: Passing classes to vararg function

本文关键字:变量 函数      更新时间:2023-10-16

我试图使一个类的行为像MS CString(也就是说,你把它传递给printf,它的行为就像一个指向C字符串的指针,没有额外的丑陋的黑魔法,如".c_str()")。

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

#include <cstdlib>
#include <cstring>
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 <cstdio>
#include "CString.hpp"
CString global_str = "global string!";
int main(int argc, char* argv[])
{
    CString str = "string";
    printf("Test: %s, %sn", str, global_str);
    return 0;
}

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

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

除此之外,以前版本的GCC还会给出一个警告+ ud2操作码。

所以…问题:我实际上可以在GCC中进行以下建设工作,或者是否有任何方法,可能不涉及C变量,使使用上述代码相同?

可以通过强制转换触发转换操作符:

printf("Test: %s, %sn", static_cast<const char*>(str), 
       static_cast<const char*>(global_str));

然而,我不知道你是否会遇到任何问题,在c++代码中避免变量可能是最好的。

使用类型安全的printf如何(来源: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<typename T, typename... Args>
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()),要么通过强制转换((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限定的)类型std::nullptr_t的参数被转换为类型void*(4.10)。在这些转换之后,如果实参没有算术、枚举、指针、指向成员的指针或类类型,则程序是病态的。传递具有非平凡复制构造函数、非平凡移动构造函数或非平凡析构函数且没有相应形参的类类型的潜在求值实参(第9条),是由实现定义的语义条件支持的。

"有条件地支持实现定义的语义"为实现留下了一个通过适当的文档来支持它的机会,但它仍然接近于未定义的行为。

如果我要这样做,我认为我应该使用可变模板设置某种中介。这样,您就可以提供一个重载(例如),当您传递一个类型为std::string的参数时,它会自动将foo.c_str()传递给printf。它(可能)会有更多的代码,但至少它会实际工作。就我个人而言,我会避免整件事,因为它带来的麻烦超过了它的价值。

写这个(丑陋的,说实话)字符串类你想解决什么?为什么不用SMTH呢?(像std::string)——在开始编写你自己的超级优化字符串之前请三思…

关于你的问题:你是真的很幸运与你的样本代码!你知道椭圆是如何工作的(在机器代码中)在C和为什么它不允许通过它传递非平凡类型吗?简而言之:printf()只是查看格式字符串,如果它在其中看到'%s',它假设下一个参数是char*,这就是全部!因此,如果您传递其他任何内容(如char, short等),它将是UB!(如果sizeof()比预期的不同,你很快就会得到一个分段错误的高概率…这就是为什么在c++中省略是一个坏习惯 !它们完全是类型不安全的!

如果你正在使用c++, 就是不要使用 C API!有很多c++库是为格式化输出而设计的(比如boost::format),而且它们是类型安全的!c++ 11为类打印函数打开了大门,但具有类型安全保证!只要读一个关于可变模板的"经典"例子……只有在阅读之后,尝试实现自己的字符串:)

不能通过变量传递对象,只能通过指向对象的指针。但是,您可以使用(可变的)基于模板的printf实现,例如c++ Format:

提供的实现。
#include "format.h"
#include "CString.hpp"
CString global_str = "global string!";
std::ostream &operator<<(std::ostream &os, const CString &s) {
  return os << static_cast<const char*>(s);
}
int main() {
  CString str = "string";
  fmt::printf("Test: %s, %sn", str, global_str);
}

如果CString正确实现,将打印"Test: string, global string!"

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

免责声明:我是这个库的作者