C++:sprintf 없이 std::string으로 fprintf 결과를 얻는 방법
문제
저는 C++로 구현된 오픈 소스 UNIX 도구를 사용하여 작업하고 있는데, 원하는 작업을 수행하려면 일부 코드를 변경해야 합니다.내 패치가 업스트림에 승인될 수 있도록 가능한 최소한의 변경을 하고 싶습니다.표준 C++로 구현 가능하고 더 많은 외부 종속성을 생성하지 않는 솔루션이 선호됩니다.
여기에 내 문제가 있습니다.저는 현재 fprintf()를 사용하여 매우 형식화된 데이터 구조를 파일 포인터에 인쇄하는 C++ 클래스("A"라고 부르겠습니다)가 있습니다.인쇄 기능에서는 여러 멤버 클래스의 동일하게 정의된 인쇄 기능을 반복적으로 호출합니다(예: "B").A 인스턴스의 print() 결과로 설정되어야 하는 std::string "foo" 멤버가 있는 또 다른 클래스 C가 있습니다.A의 to_str() 멤버 함수로 생각하세요.
의사코드에서:
class A {
public:
...
void print(FILE* f);
B b;
...
};
...
void A::print(FILE *f)
{
std::string s = "stuff";
fprintf(f, "some %s", s);
b.print(f);
}
class C {
...
std::string foo;
bool set_foo(std::str);
...
}
...
A a = new A();
C c = new C();
...
// wish i knew how to write A's to_str()
c.set_foo(a.to_str());
C는 상당히 안정적이지만 A와 B(및 A의 나머지 종속 항목)는 유동적 상태에 있으므로 필요한 코드 변경이 적을수록 좋습니다.현재 인쇄(FILE* F) 인터페이스도 보존해야 합니다.나는 A::to_str()을 구현하는 여러 가지 접근 방식을 고려했는데 각각 장점과 단점이 있습니다.
fprintf() 호출을 sprintf()로 변경합니다.
- 형식 문자열을 다시 작성할 필요가 없습니다.
- print()는 다음과 같이 다시 구현될 수 있습니다:fprint(f, this.to_str());
- 하지만 수동으로 char[]을 할당하고 많은 c 문자열을 병합한 다음 마지막으로 문자 배열을 std::string으로 변환해야 합니다.
문자열 스트림에서 a.print()의 결과를 포착해 보세요.
- 모든 형식 문자열을 << 출력 형식으로 변환해야 합니다.변환할 fprintf()가 수백 개 있습니다. :-{
- UNIX 파일 핸들에서 출력 스트림을 생성하는 표준 방법이 없기 때문에 print()를 다시 작성해야 합니다. 그 사람은 그럴 수도 있다고 하던데).
Boost의 문자열 사용 형식 라이브러리
- 더 많은 외부 종속성.왝.
- Format의 구문은 짜증날 만큼 printf()와 다릅니다.
printf(format_str, args) -> cout << Boost::format(format_str) % arg1 % arg2 % 등
Qt를 사용하세요 QString::asprintf()
- 다른 외부 종속성.
그렇다면 가능한 모든 옵션을 다 써버린 걸까요?그렇다면, 어떤 것이 최선의 선택이라고 생각하시나요?그렇지 않다면 내가 무엇을 간과했는가?
감사해요.
해결책
나는 #3을 사용하고 있습니다:부스트 문자열 형식 라이브러리 - 하지만 형식 사양의 차이로 인해 어떤 문제도 발생한 적이 없다는 점을 인정해야 합니다.
나에게는 매력적으로 작동하며 외부 종속성은 더 나쁠 수 있습니다(매우 안정적인 라이브러리).
편집됨:printf 대신 Boost::format을 사용하는 방법에 대한 예제를 추가합니다.
sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);
Boost::format 라이브러리를 사용하면 다음과 같습니다.
string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);
이것이 Boost::format의 사용법을 명확히 하는 데 도움이 되기를 바랍니다.
저는 4~5개의 응용 프로그램(파일에 형식화된 문자열 작성 또는 로그 파일에 사용자 정의 출력 작성)에서 sprintf/printf 대체 방법으로 Boost::format을 사용했으며 형식 차이로 인해 문제가 발생한 적이 없습니다.다르게 나타나는 일부(다소 모호한) 형식 지정자가 있을 수 있지만 문제는 발생하지 않았습니다.
대조적으로 나는 (내가 기억하는 한) 스트림으로는 실제로 할 수 없는 몇 가지 형식 사양을 가지고 있었습니다.
다른 팁
다음은 기능을 'sprintf'와 동일하게 만들면서 std::string을 반환하고 버퍼 오버플로 문제에 영향을 받지 않는 관용구입니다.이 코드는 제가 작성 중인 오픈 소스 프로젝트(BSD 라이센스)의 일부이므로 누구나 원하는 대로 자유롭게 사용할 수 있습니다.
#include <string>
#include <cstdarg>
#include <vector>
#include <string>
std::string
format (const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
std::string buf = vformat (fmt, ap);
va_end (ap);
return buf;
}
std::string
vformat (const char *fmt, va_list ap)
{
// Allocate a buffer on the stack that's big enough for us almost
// all the time.
size_t size = 1024;
char buf[size];
// Try to vsnprintf into our buffer.
va_list apcopy;
va_copy (apcopy, ap);
int needed = vsnprintf (&buf[0], size, fmt, ap);
// NB. On Windows, vsnprintf returns -1 if the string didn't fit the
// buffer. On Linux & OSX, it returns the length it would have needed.
if (needed <= size && needed >= 0) {
// It fit fine the first time, we're done.
return std::string (&buf[0]);
} else {
// vsnprintf reported that it wanted to write more characters
// than we allotted. So do a malloc of the right size and try again.
// This doesn't happen very often if we chose our initial size
// well.
std::vector <char> buf;
size = needed;
buf.resize (size);
needed = vsnprintf (&buf[0], size, fmt, apcopy);
return std::string (&buf[0]);
}
}
편집하다:이 코드를 작성할 때 C99 준수가 필요하다는 것과 Windows(이전 glibc도 포함)에 필요한 공간의 양을 결정하는 척도가 아니라 실패 시 -1을 반환하는 다른 vsnprintf 동작이 있다는 사실을 전혀 몰랐습니다. .여기에 제가 수정한 코드가 있습니다. 모두가 살펴보고 괜찮다고 생각하시면 해당 비용만 나열되도록 다시 편집하겠습니다.
std::string
Strutil::vformat (const char *fmt, va_list ap)
{
// Allocate a buffer on the stack that's big enough for us almost
// all the time. Be prepared to allocate dynamically if it doesn't fit.
size_t size = 1024;
char stackbuf[1024];
std::vector<char> dynamicbuf;
char *buf = &stackbuf[0];
va_list ap_copy;
while (1) {
// Try to vsnprintf into our buffer.
va_copy(ap_copy, ap);
int needed = vsnprintf (buf, size, fmt, ap);
va_end(ap_copy);
// NB. C99 (which modern Linux and OS X follow) says vsnprintf
// failure returns the length it would have needed. But older
// glibc and current Windows return -1 for failure, i.e., not
// telling us how much was needed.
if (needed <= (int)size && needed >= 0) {
// It fit fine so we're done.
return std::string (buf, (size_t) needed);
}
// vsnprintf reported that it wanted to write more characters
// than we allotted. So try again using a dynamic buffer. This
// doesn't happen very often if we chose our initial size well.
size = (needed > 0) ? (needed+1) : (size*2);
dynamicbuf.resize (size);
buf = &dynamicbuf[0];
}
}
setw() 호출 및 iomanip의 기타 호출과 같은 형식화와 함께 std::string 및 iostreams를 사용할 수 있습니다.
다음은 대체 솔루션일 수 있습니다.
void A::printto(ostream outputstream) {
char buffer[100];
string s = "stuff";
sprintf(buffer, "some %s", s);
outputstream << buffer << endl;
b.printto(outputstream);
}
(B::printto
유사) 정의하고
void A::print(FILE *f) {
printto(ofstream(f));
}
string A::to_str() {
ostringstream os;
printto(os);
return os.str();
}
물론 버퍼 오버플로를 방지하려면 sprintf 대신 snprintf를 사용해야 합니다.더 위험한 sprintfs를 선택적으로 << 형식으로 변경하여 더 안전하면서도 최소한으로 변경할 수도 있습니다.
Loki 라이브러리의 SafeFormat 헤더 파일(http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf).Boost의 문자열 형식 라이브러리와 유사하지만 printf(...) 함수의 구문을 유지합니다.
이게 도움이 되길 바란다!
직렬화에 관한 것입니까?아니면 인쇄가 제대로 되나요?전자라면 Boost::serialization도 고려해보세요.이는 객체와 하위 객체의 "재귀적" 직렬화에 관한 것입니다.
{fmt} 라이브러리 제공하다 fmt::sprintf
수행하는 기능 printf
-호환 가능한 형식 지정(다음에 따른 위치 인수 포함) POSIX 사양) 결과를 다음과 같이 반환합니다. std::string
:
std::string s = fmt::sprintf("The answer is %d.", 42);
부인 성명:나는 이 도서관의 저자입니다.