2012-03-07 21 views
12

Trong dự án của chúng tôi, chúng tôi sử dụng toán tử dòng C++ (< <) trong mô hình đối tượng để in ra định dạng dễ đọc. Một ví dụ đơn giản là thế này:Cách thêm indention vào toán tử luồng

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n"; 
    oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n"; 
} 

Hệ quả này trong khai thác gỗ:

[SomeMember1: foo] 
[SomeMember2: bar] 

gì chúng tôi muốn bây giờ là có thể thụt kết quả của điều hành đó. Một số lớp gọi điện có thể không muốn kết quả như thế này, nhưng muốn thêm 2 khoảng trống vào trước mỗi dòng. Chúng tôi có thể thêm một thành viên vào lớp của chúng tôi chỉ định sự chú ý, nhưng điều đó dường như không phải là một giải pháp thanh lịch.

Tất nhiên đây không phải là vấn đề rất lớn, nhưng việc đăng nhập của chúng tôi sẽ đẹp hơn rất nhiều nếu điều này có hiệu quả.

Cảm ơn

Trả lời

21

Giải pháp đơn giản nhất là trượt một streambuf lọc giữa ostream và streambuf thực tế. Một cái gì đó như:

class IndentingOStreambuf : public std::streambuf 
{ 
    std::streambuf*  myDest; 
    bool    myIsAtStartOfLine; 
    std::string   myIndent; 
    std::ostream*  myOwner; 
protected: 
    virtual int   overflow(int ch) 
    { 
     if (myIsAtStartOfLine && ch != '\n') { 
      myDest->sputn(myIndent.data(), myIndent.size()); 
     } 
     myIsAtStartOfLine = ch == '\n'; 
     return myDest->sputc(ch); 
    } 
public: 
    explicit   IndentingOStreambuf( 
          std::streambuf* dest, int indent = 4) 
     : myDest(dest) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(NULL) 
    { 
    } 
    explicit   IndentingOStreambuf(
          std::ostream& dest, int indent = 4) 
     : myDest(dest.rdbuf()) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(&dest) 
    { 
     myOwner->rdbuf(this); 
    } 
    virtual    ~IndentingOStreambuf() 
    { 
     if (myOwner != NULL) { 
      myOwner->rdbuf(myDest); 
     } 
    } 
}; 

Để chèn, chỉ cần tạo một thể hiện của các streambuf:

IndentingOStreambuf indent(std::cout); 
// Indented output... 

Khi indent đi ra khỏi phạm vi, tất cả mọi thứ trở lại bình thường.

(Đối với khai thác gỗ, tôi có một trong đó là phức tạp hơn một chút: các LoggingOStreambuf mất __FILE____LINE__ như các đối số, đặt myIndent thành một chuỗi định dạng với những lập luận này, cộng với một tem thời gian , reset nó vào một thụt đầu dòng chuỗi sau mỗi lần sản lượng, thu thập tất cả các đầu ra trong một std::ostringstream, và đầu ra nó atomically để myDest trong destructor.)

+0

Đã hoạt động hoàn hảo! Mặc dù vậy, tôi đã thực hiện một vài thay đổi, như thêm phương thức increaseIndent và reduceIndent. Nhật ký của tôi trông giống như cách tôi muốn chúng ngay bây giờ. Cảm ơn. –

+0

@ James: Bạn có sẵn mã phức tạp hơn không? – Cookie

1

Cách không tốt để làm điều này là thêm biến toàn cục, cho biết dấu đầu dòng. Một cái gì đó như thế này:

std::string OwnClassIndentation; 
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << "[SomeMember1:" << OwnClassIndentation << iOwnClass._ownMember1 << "]\n"; 
    oStream << "[SomeMember2:" << OwnClassIndentation << iOwnClass._ownMember2 << "]\n"; 
} 

Và sau đó đặt nó là thích hợp.

+2

Nếu bạn chỉ muốn thụt đầu dòng khi xuất ra một loại hình cụ thể, mà 'operator <<' bạn xác định , sau đó bạn có thể sử dụng 'std :: ios_base :: xalloc' và' std :: ios_base :: iword' t o duy trì một dòng thụt đầu dòng. Nếu bạn muốn thụt lề ngay cả khi xuất ra 'dest <<" một số chuỗi "', tuy nhiên, bạn sẽ cần phải sử dụng một streambuf lọc. –

+0

@JamesKanze: Điểm tốt - điều tương tự cũng áp dụng cho giải pháp của tôi - không nghĩ về điều đó. –

+0

Điều này chắc chắn sẽ hoạt động, nhưng tôi thực sự không thích các biến toàn cục, đặc biệt khi chúng chỉ cần định dạng nhật ký của tôi :) –

0

Bạn có thể tạo lớp luồng của riêng mình có biến thụt lề và ghi đè phần cuối cho lớp đó, chèn thụt đầu dòng.

+2

đầu ra đầu tiên (có thể hoặc không thể là một tính năng), sẽ chèn dấu cách sau dòng cuối cùng (hành vi không xác định nếu tệp đang mở ở chế độ văn bản) và sẽ không thụt lề khi đầu ra ''\ n''. Và tất nhiên, bạn sẽ phải định nghĩa tất cả các toán tử '<<'. Đây là một công việc cho 'streambuf', không phải là lớp' ostream'. –

+0

Tôi cũng nghĩ về điều này, nhưng cái tôi phải viết, không phải trong tay, vì vậy tôi không thể thay đổi kiểu 'của nó. Tôi phải sử dụng ostream từ lib đăng nhập của chúng tôi. Streambuf đã hoạt động. –

+0

@W. Goeman: bạn có thể lấy được từ ostream, nó có các phương thức ảo. – Dani

1

Điều này có thể được thực hiện bằng cách sử dụng trình xử lý luồng tùy chỉnh lưu trữ mức thụt lề mong muốn trong một từ của mảng mở rộng nội bộ của luồng. Bạn có thể yêu cầu một từ như vậy bằng cách sử dụng hàm ios_base::xalloc. Hàm này sẽ cho bạn chỉ mục từ của bạn. Bạn có thể truy cập nó bằng cách sử dụng ios_base::iword. Một cách để thực hiện điều này sẽ là:

struct indent { 
    indent(int level) : level(level) {} 
private: 
    friend std::ostream& operator<<(std::ostream& stream, const indent& val); 

    int level; 
}; 

std::ostream& operator<<(std::ostream& stream, const indent& val) { 
    for(int i = 0; i < val.level; i++) { 
     stream << " "; 
    } 
    return stream; 
} 

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << indent(oStream.iword(index)) << "[SomeMember1: " << 
       iOwnClass._ownMember1 << "]\n"; 
    oStream << indent(oStream.iword(index)) << "[SomeMember2: " << 
       iOwnClass._ownMember2 << "]\n"; 
} 

Bạn sẽ phải tìm ra nơi để lưu trữ index. Điều này có hiệu quả cho phép bạn thêm trạng thái tùy chỉnh vào luồng (lưu ý rằng đây không phải là luồng an toàn ngoài luồng). Mỗi hàm muốn thụt đầu dòng nên thêm thụt lề được yêu cầu vào luồng và trừ nó lại khi nó được thực hiện. Bạn có thể đảm bảo điều này luôn luôn xảy ra bằng cách sử dụng một người bảo vệ để thêm/bớt các indent mong muốn (IMHO này là hơn tao nhã vì sử dụng một thao túng):

class indent_guard { 
public: 
    indent_guard(int level, std::ostream& stream, int index) 
    : level(level), 
     stream(stream), 
     index(index) 
    { 
     stream.iword(index) += level; 
    } 

    ~indent_guard() { 
     stream.iword(index) -= level; 
    } 

private: 
    int level; 
    std::ostream& stream; 
    int index; 
}; 

Bạn có thể sử dụng nó như thế này:

void some_func() { 
    indent_guard(2, std::cout, index); 

    // all output inside this function will be indented by 2 spaces 

    some_func(); // recursive call - output will be indented by 4 spaces 

    // here it will be 2 spaces again 
} 
+0

Ít nhất bạn cũng biết 'iostream' :-). Đây rõ ràng là cách để xử lý bất kỳ thao tác đặc biệt nào cho các lớp tùy chỉnh. Nó không giúp đỡ nếu đầu ra đầu tiên không phải là một loại được xác định bởi chính bạn; trong trường hợp này, bạn cần phải chặn đầu ra ở mức 'streambuf'. –

+0

Điều này đã không hoàn toàn giải quyết vấn đề của tôi, nhưng chắc chắn là một "tốt để biết". Cảm ơn. –

+0

'some_func()' của bạn sẽ gây ra tràn ngăn xếp. – uckelman