2016-05-04 7 views
14

Я немного кручусь с потоками и не могу опустить голову вокруг следующего.Как обнаружить, что ptr все еще ссылается на действительную ссылку после того, как эта ссылка выходит из области действия

Здесь у нас есть базовый ptr, который настроен на разные выходные потоки, независимо от того, cout, cerr или file.

// ostream ptr 
std::ostream* outstream; 

// set output ostream 
void setOutput(std::ostream & os) 
{ 
    outstream = &os; 
} 

// write message to ostream 
void writeData(const std::string & msg) 
{  
    *outstream << msg << '\n'; 
} 

int main (int argc, char * const argv[]) 
{ 
    // init to std out 
    setOutput(std::cout); 
    writeData("message to cout"); 

    setOutput(std::cerr); 
    writeData("message to cerr"); 

    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
    //fileout.close(); 

    setOutput(std::cout); 
    writeData("message2 to cout"); 

    return 0; 
} 

Вышеописанное прекрасно работает и демонстрирует силу реализации iostream C++. Отлично.

Однако, поскольку setOutput задается ссылкой, ссылочный объект должен оставаться в области видимости. Именно здесь возникает проблема. Я хочу выяснить способ вывода по умолчанию на std::cout, если поток или любой другой поток недействителен. То есть ссылочный объект выходит за пределы области видимости.

Например:

// write message to ostream 
void writeData(const std::string & msg) 
{ 
    if (/*stream or memory is invalid*/) 
    setOutput(std::cout); 

    *outstream << msg << '\n'; 
} 
// local fileout goes out of scope 
void foo() 
{ 
    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
} 

int main (int argc, char * const argv[]) 
{ 
    setOutput(std::cout); 
    writeData("message to cout"); 

    foo(); 
    /* problem the local fileout is no longer referenced by the ostream ptr*/ 
    /* the following should be redirected to std::cout cuz of default*/ 
    writeData("message2 to cout"); 

    return 0; 
} 

выше отлично, пока foo() возвращается к основной функции. Там это выглядит ужасно, потому что локально определенный ofstream недоступен.

Очевидно, это нецелесообразно, и пользователь должен это осознать. Однако я хочу обернуть все это в класс журнала и, таким образом, сохранить состояние объекта, даже считая, что это неправильное использование может случиться. Это приведет к недействительным нарушениям доступа, которые трудно найти.

Конкретный вопрос. Есть ли способ выяснить, все ли ptr или любой ptr по-прежнему ссылаются на действительный объект или ячейку памяти?

пс: Я мог бы использовать кучу памяти и сделать что-то со смарт-указатели, но честно говоря, я бы хотел, чтобы это так, если это возможно

+3

«Конкретный вопрос. Есть ли способ выяснить, все ли ptr или любой ptr по-прежнему ссылаются на действительный объект или ячейку памяти?» - да: Java. – nicomp

+6

@nicomp не нравится java – Montaldo

+0

@Captain Obvlious, но все это в стеке. Я пробовал это, но вы все еще не можете определить, вышел ли он из сферы действия afaik – Montaldo

ответ

13

Бетон вопрос. Есть ли способ выяснить, все ли ptr или любой ptr по-прежнему ссылаются на действительный объект или ячейку памяти?

Нет. Нет способа понять это с помощью сырых указателей. По крайней мере, не в стандартном C++.

Вам нужно будет гарантировать, что заостренный объект остается живым до тех пор, пока он направлен.

Обычный шаблон, который используется для обеспечения гарантии RAII, как подробно описано в других ответах. Другим подходом к обеспечению достоверности указателя является использование интеллектуального указателя вместо исходного. Однако они несовместимы с автоматическими переменными.

Было бы хорошо продолжать указывать на мертвые объекты, пока вы могли бы гарантировать, что указатель не будет разыменован. Что часто трудно гарантировать, потому что, как уже было сказано, нет возможности проверить, существует ли заострённый объект.

+0

Что такое «сырой» указатель? – nicomp

+2

@ rawomp 'raw' указатели являются правильными указателями. Я использовал «сырой» квалификатор, чтобы отличать от интеллектуальных указателей, которые также можно считать указателями с абстрактной точки зрения, хотя они не являются правильными указателями.Мне нужно было различать, потому что в случае некоторых интеллектуальных указателей вы можете * выяснить, остается ли заостренный объект действительным, а в случае других - сам интеллектуальный указатель заботится об уничтожении объекта, когда он больше не ссылается поэтому нет необходимости выяснять это. – user2079303

+3

@nicomp Чтобы расширить комментарий пользователя2078303, желание иметь возможность делать то, что хочет сделать OP, так велико, что мы изобрели всевозможные «указатели» (классы, которые указывают), которые предоставляют гарантии относительно продолжительности жизни объект. Они настолько популярны, что люди начали называть указатели «необработанные указатели», чтобы проявлять особую осторожность, чтобы указать, как мало гарантий они приходят, и мягко поощрять людей использовать более интеллектуальные указатели. Я также слышал о них, называемых «родными указателями», но «raw» более распространен. –

3

Возможный подход заключается в создании класса RAII, который оборачивает поток перед тем передавая его в setOutput. Этот класс должен быть разработан так, чтобы работать как shared_ptr, так что он поддерживает общий счетчик ссылок. writeData затем проверяет, имеет ли он единственную оставшуюся ссылку, и если это так, то уничтожает ostream и по умолчанию - cout.

10

Это звучит как отличный вариант использования для RAII.

Напишите класс, который принимает имя файла и std::ostream** в качестве параметров его конструктору. В конструкторе указанного класса создайте поток (как элемент) и установите указатель на поток. В деструкторе вернитесь к stdout.

Затем замените первые две строки следующей функции декларацией нового класса.

void foo() 
{ 
    std::ofstream fileout("test.txt", std::ofstream::out | std::ofstream::app); 
    setOutput(fileout); 
    writeData("message to file"); 
} 
5

Вы должны использовать RAII, чтобы принудительно установить поток, а затем вернуть его в std :: cout, если объект был уничтожен.

class OutputStream 
{ 
    protected: 
     static std::ostream*& internalGlobalStateOfOutputStream() 
     { 
      static std::ostream* out = &std::cout; 
      return out; 
     } 
    public: 
     static std::ostream& getOutputStream() 
     { 
      return *internalGlobalStateOfOutputStream(); 
     } 
}; 
template<typename T> 
class OutputStreamOwner: public OutputStream 
{ 
    T ownedStream; 
    public: 
     OutputStreamOwner(T&& obj) 
      : ownedStream(std::move(obj)) 
     { 
      internalGlobalStateOfOutputStream() = &ownedStream; 
     } 
     template<typename... Args> 
     OutputStreamOwner(Args... args) 
      : ownedStream(args...) 
     { 
      internalGlobalStateOfOutputStream() = &ownedStream; 
     } 
     ~OutputStreamOwner() 
     { 
      internalGlobalStateOfOutputStream() = & std::cout; 
     } 
     // Delete copy 
     OutputStreamOwner(OutputStreamOwner const&)   = delete; 
     OutputStreamOwner& operator(OutputStreamOwner const&) = delete; 
}; 

Использование является:

void foo() 
{ 
    OutputStreamOwner<std::ofstream> output("test.txt", std::ofstream::out | std::ofstream::app); 

    writeData("message to file"); 
} 
1

Бетон вопрос. Есть ли какой-либо способ выяснить, все ли ссылки на действительный объект или ptr или любой ptr по-прежнему ссылаются на действительный объект или ?

пс: Я мог бы использовать кучу памяти и сделать что-то со смарт-указатели, но честно говоря, я бы хотел, чтобы это так, если это возможно

Нет, нет стандартного способа проверить, если сырой указатель или ссылка по-прежнему относится к действительному объекту.

RAII - это стандартное решение на C++ для такого типа проблем, поэтому, на мой взгляд, вы должны смотреть на интеллектуальные указатели. Я не знаю ни одной библиотеки, предоставляемой умным указателем, которая решила бы эту конкретную проблему, но решение RAII, основанное на совместной собственности, кажется лучшим решением здесь.

2

Вы можете избежать этих осложнений в целом с помощью функции, которая принимает поток в качестве входного сигнала.

void writeData(std::ostream& os, const std::string & msg) 
{  
    os << msg << '\n'; 
} 

Вы можете уточнить его, возвращая поток, чтобы один к цепи вызовов к нему:

std::ostream& os writeLine(std::ostream& os, const std::string & msg) 
{  
    os << msg << '\n'; 
    return os; 
} 

// declare stream 
stream << writeLine(stream, "Foo") << writeLine(stream, "Bar"); 

На самом деле эта функция лучше и проще в обслуживании, так как у вас нет чтобы запомнить, какой поток установлен в любой момент времени. Для крупных программ это важное качество.