Sunday, March 29, 2015

Optimal use of std::ifstream to read into std::string

This is an exemplary bit of code to help read a file by std::ifstream and avoid needless buffer reallocations.
1. DataPath, returns a "Data" subdirectory inside application bundle. In Xcode to package resource files along with a bundle, add the resource directory to a project as a directory reference. Don't name it as "Resources", internal packaging system reserves that name (for whatever reason) and will throw an annoying "iOS Simulator failed to install the application" error.
So, I've chosen to scope resources inside a "Data" directory.
2. ReadFile, will read entire file content and return in a std::string. Will work for cross-platform file access, just paste in a .cpp file.


<FileSystem.hpp>

#include <string>


namespace FileSystem
{
    
    std::string DataPath();
    
    std::string ReadFile(const char* filePath);
    
}



<FileSystem.mm>

#import <Foundation/Foundation.h>
#include <iostream>
#include <fstream>
#import "FileSystem.hpp"


namespace FileSystem
{

    using namespace std;
    
    string DataPath()
    {
        string result;
        NSBundle* bundle = [NSBundle mainBundle];
        
        if (bundle == nil) {
            #ifdef DEBUG
                NSLog(@"Bundle is nil... which should never happen.");
            #endif
        } else {
            NSString* path = [bundle bundlePath];
            // Also, to get Documents directory:
            // path = [NSHomeDirectory() stringByAppendingString: @"/Documents/"];
            path = [NSString stringWithFormat: @"%@%s", path, "/Data/"];
            result = string([path UTF8String]);
        }
        
        return result;
    }
    
    string ReadFile(const char* filePath)
    {
        string result;
        
        ifstream ifs(filePath, ios::in | ios::binary | ios::ate);
        if (!ifs) {
            throw invalid_argument(string("Error opening '") + filePath + "'.");
        }
        
        auto fileSize = ifs.tellg();
        result.resize((string::size_type)fileSize);
        
        ifs.seekg(0, ios::beg);
        auto buf = &result[0];
        ifs.read(buf, (streamsize)fileSize);
        return result;
    }

}