1. Trang chủ
  2. » Kỹ Thuật - Công Nghệ

CRC.Press A Guide to MATLAB Object Oriented Programming May.2007 Episode 2 Part 8 pps

20 309 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 529,47 KB

Nội dung

Like member functions, public member variables can include pass-by-reference capability.. Line 6 sets do_assignin to true because now we want draw to assign the object before it exits..

Trang 1

environment, pass-by-reference syntax can reduce code quality and interfere with debugging If you press me, I recommend that you use pass-by-reference very sparingly Even so, if you decide

to include pass-by-reference syntax, it’s important to have a good strategy In the examples that follow, we will implement one possible strategy

Currently all functions in the shape hierarchy use standard pass-by-value syntax Consequently, all mutators must return a copy of the modified object, and a user must assign the returned object

to a variable This works well whenever it is obvious that a function is a mutator For some functions,

it is difficult to decide at a glance whether the function is a mutator In the shape hierarchy, draw

is a mutator but this fact is easy to forget Calling draw modifies mFigureHandle, but mFig-ureHandle is an internal implementation detail Clients should be shielded from internal details, and pass-by-reference is another tool that can be used to help enforce the separation between public and private

The current implementation of draw includes the following test:

if nargout ~= 1

warning(‘draw must be called using: obj = draw(obj)’);

else

Since all side effects from draw are private, a client might reasonably expect to call draw without assigning the return value In the current implementation that would result in an error The client doesn’t get the desired result, but at least the error message tells what to do With pass-by-reference emulation, draw might still perform a nargout test; however, the result of the test would no longer throw an error Instead, pass-by-reference emulation would be used to modify the input object in place The change would be immediately reflected in the client’s workspace In the examples that follow, we will modify draw by allowing it to be called as a mutator or with pass-by-reference syntax

Like member functions, public member variables can include pass-by-reference capability This allows dot-reference operations with accessor syntax to include hidden side effects These side effects require pass-by-reference assignment of the input object We will demonstrate this behavior

by modifying a class in the cShape hierarchy Rather than change the operation of an existing public variable, we will invent a new public variable called View When View is true, the object will display itself using developer view format; and when View is false, normal display format will be used To demonstrate the pass-by-reference operation, anytime View is accessed, the accessor will change the value stored in developer_view With standard pass-by-value, this change would be lost; however, with pass-by-reference, changes to the object are preserved

21.2 PASS-BY-REFERENCE FUNCTIONS

The three commands used to implement pass-by-reference emulation are inputname, assignin, and evalin These standard MATLAB functions are described as follows:

inputname(argument_number): This function looks in the caller’s workspace and returns the name of the variable associated with the input argument at position

argument_number Thus, we can find the name of the object in the caller’s workspace

by calling inputname for this

assignin(‘caller’, ‘name’, value): This function assigns a value to a variable in the caller’s workspace To do this, you have to know the name of the variable

in the caller’s workspace The name can be inferred from a standard naming convention,

or the name can be obtained using inputname

Trang 2

Pass-by-Reference Emulation 315

evalin(‘caller’, ‘expression’): This function evaluates an expression in

the caller’s workspace Almost any expression can be evaluated Evalin allows a

function to gather a lot of information about the caller and the caller’s workspace

The biggest limitation in pass-by-reference emulation occurs when inputname returns an

empty string This happens when the input is not a pure variable but rather the result of an operation

Among others, some common syntax examples that results in an empty inputname are x(1),

x+y, s.val, and varargin{:} Anytime our code uses inputname, we must remember to

check for an empty string and take the appropriate course of action In most cases of

pass-by-reference emulation, the correct action is an error

21.3 PASS-BY-REFERENCE DRAW

Currently draw throws an error when nargout is zero By adding inputname and assignin,

we can still assign the mutated object even when nargout is zero Additions to the beginning

and end of draw are provided in Code Listing 130 All tailored versions of draw must include

these additions

The same as before, line 3 checks the number of output arguments If nargout is one, line

4 sets the variable do_assignin to false Draw does not need to assign the object in place

because the caller is correctly using pass-by-value syntax If there is no output argument, lines

6–10 initialize the pass-by-reference variables Line 6 sets do_assignin to true because now

we want draw to assign the object before it exits Line 7 tries to get the name of the object in the

caller’s workspace If inputname returns an empty string, lines 8–10 throw an error

The comment in line 13 is a placeholder for the body of code normally included in draw We

have listed those commands before and don’t need to list them again At the end of draw, if

do_assignin is true, line 16 performs the pass-by-reference assignment The value of

callers_this is the name of the object in the caller’s workspace found on line 7

Code Listing 130, An Approximation to Call-by-Reference Behavior

1 function this = draw(this, figure_handle)

2

3 if nargout == 1

4 do_assignin = false;

6 do_assignin = true;

7 callers_this = inputname(1);

8 if isempty(callers_this)

9 error('must be called using mutator or call-by-reference

syntax')

11 end

12

13 % The guts of draw goes here

14

15 if do_assignin

16 assignin('caller', callers_this, this);

17 end

Trang 3

Draw now supports mixed use of either call-by-value or call-by-reference syntax The normal call-by-value syntax doesn’t change It still looks like the following:

shape = draw(shape);

The new call-by-reference syntax omits the assignment Call by reference syntax looks like the following:

draw(shape);

Supporting both methods is not a requirement, but it is usually the right thing to do

21.4 PASS-BY-REFERENCE MEMBER VARIABLE: VIEW

If one member function can benefit from pass-by-reference behavior, maybe others can benefit too Functions outside the group of eight can use the same technique demonstrated for draw Functions included in the group of eight follow a different approach For these functions, do_assignin

isn’t assigned based on nargout, but rather it is passed from a helper function into get or set The helper function must be the controlling source because MATLAB already uses syntax to decide whether to call subsref or subsasgn

The other wrinkle in pass-by-reference emulation involves the number of function calls typically found between a client’s use of operator syntax and the helper For example, dot-reference syntax

is converted into a call to subsref, which calls get, which potentially calls a parent version of

get, which finally calls the helper If the helper needs to specify pass-by-reference operation, that request must travel all the way back into subsref The helper-function interface described in Chapter 16 gives the helper a way to kick off the process The intervening functions must now accept the arguments and make the correct assignments

As always, anchoring the example to some particular requirement makes the discussion easier

to follow As previously described, we will create a new public member named View When View

is true, the object displays using developer view format; and when View is false, the normal display format is used Like all public variables, the logical value of View may be assigned using

a dot-reference operator, by calling subsasgn, or by calling set

Unlike other public variables, we are going to add pass-by-reference behavior to View and do something that makes pass-by-reference relatively easy to observe In this example, except for the fact that it demonstrates pass-by-reference mechanics, the behavior is pointless When View is accessed, the helper will appear to use the ~ operator to reverse value of a private logical variable The helper returns this new value, the modified object, and do_assignin with a value of true Ultimately, the modified object is assigned into the client’s workspace The assignment relies on pass-by-reference code inserted into get and subsref by Class Wizard

21.4.1 H ELPERS , GET , AND SUBSREF WITH P ASS - BY -R EFERENCE B EHAVIOR

The helper initiates pass-by-reference by mutating the object and passing back a true value in

do_assignin Inside get, the do_assignin value triggers pass-by-reference commands similar to those added to draw There are a few differences because get is usually an intermediate function That is, get is usually called indirectly through subsref, not directly by the client In this situation, the proper assignment of do_assignin uses assignin This is where code organization in the group of eight proves its worth The block organization in get and subsref

makes it easier to support a general method for call-by-reference emulation The following example code demonstrates the general implementation

Trang 4

21.4.1.1 Pass-by-Reference Behavior in the Helper

Earlier versions of cShape did not include a public variable named View The example files in this chapter’s directory include View as an addition From previous experience, we know we can add a new public variable without introducing unexpected side effects In the Class Wizard Public Variables … dialog, add a new public variable named View and enter %helper in both the

Accessor Expression and Mutator Expression fields No further changes are required because the value of View relies on private variables that already exist Save the change and rebuild the files Class Wizard writes an initial version of View_helper.m into /@cShape/private/ The initial version must always be tailored to match the desired behavior The tailored version is shown in Code Listing 131

Code Listing 131, Enabling a Helper with Call-by-Reference Behavior

1 function [do_sub_indexing, do_assignin, this, varargout] = .

2 View_helper(which, this, index, varargin)

3

4 switch which

5 case 'get' % ACCESSOR

6 % input: index contains any additional indexing as a

substruct

7 % input: varargin empty for accessor

8 do_sub_indexing = true; % tells get.m whether to index

deeper

9 do_assignin = true; % !!! call-by-reference behavior

10 varargout = cell(1, nargout-3); % 3 known vars plus

varargout 11

12 % before the toggle [] means standard, load after-toggle

values

13 developer_sieve = cellfun('isempty',

{this.mDisplayFunc});

14 % toggle the display function, remember false means standard

15 [this(developer_sieve).mDisplayFunc] =

deal('developer_view');

16 [this(~developer_sieve).mDisplayFunc] = deal([]);

17

18 % fill varargout with the "requested" data

19 varargout = num2cell(developer_sieve);

20

21 case 'set' % MUTATOR

22 % input: index contains any additional indexing as a

substruct

23 % input: varargin contains values to be assigned into

the object

24 do_sub_indexing = false; % mutator _must_ do deep indexing

Trang 5

First, let’s tackle ‘get’, the accessor On line 8, accepting the value of true allows code that already exists in get to handle any additional indices On line 9, the value returned via

do_assignin controls pass-by-reference emulation Here, the normal return value of false

has been changed to true When get receives this true value, it will trigger a series of pass-by-reference commands Next, the helper implements the desired behavior

Line 10 preallocates varargout Lines 13–19 use vector syntax to both toggle the value and fill varargout Vector syntax is always preferred because it is more efficient when this is nonscalar The value associated with the public variable View is determined by the value of the private variable mDisplayFunc Line 13 uses cellfun and ‘isempty’ to determine the logical View values returned through varargout Lines 15–16 toggle the view state by assigning

‘developer_view’ into the empty elements and empty into the others This is where the mutation occurs In an ordinary accessor, this change would never make it back to the client; however, the value returned due to change on line 9 means that this accessor is no ordinary accessor Line 19 assigns the public View values into varargout

Mutator code is conventional Lines 24–26 accept the code provided by Class Wizard In each object, View is scalar, so lines 28–30 throw an error if indexing deeper than the first dot-reference level is detected Nonscalar public variables often require a block of code to handle deeper indexing levels Line 32 converts the input cell array into a logical array, and lines 34–35 use the logical array to assign either ‘developer_view’ or empty into the proper elements

21.4.1.2 Pass-by-Reference Code in get.m

Commands in get are organized into blocks that represent public variables, concealed variables, and parent slice and forward Variables in each block are also classified as either direct-link or direct-link Direct-link variables associate one-to-one with private member variables, while non-direct-link variables use a helper function The distinction is important because pass-by-reference behavior can only be initiated by a helper Since direct-link variables don’t use a helper, they cannot initiate pass-by-reference behavior This is not a serious limitation because any direct-link variable can be easily converted into a non-direct-link variable There are no side effects, and Class Wizard automatically generates most of the non-direct-link code

25 do_assignin = false; % leave false until you read book

section 3

26 varargout = {}; % 'set' returns nothing in varargout 27

28 if ~isempty(index)

29 error('Deeper levels of indexing is not supported');

31 % true in varargin means developer view

32 developer_sieve = logical([varargin{:}]);

33 % set the display function

34 [this(developer_sieve).mDisplayFunc] =

deal('developer_view');

35 [this(~developer_sieve).mDisplayFunc] = deal([]);

36

37 otherwise

38 error('OOP:unsupportedOption', ['Unknown helper option: '

which]);

39 end

Trang 6

Inside get, each non-direct-link case includes a properly configured call to a helper function Values returned by the helper function may trigger by-reference behavior The primary pass-by-reference code block can be found in chapter_0/@cShape/get.m beginning on line 175 The pass-by-reference block has been copied into Code Listing 132

The test in line 175 guards entry into block Pass-by-reference commands execute only when

do_assignin is true The first pass-by-reference command, line 176, uses the inputname

command to obtain the client’s name for the object If inputname(1) returns an empty string, pass-by-reference assignment cannot be completed and lines 178–180 issue a warning The con-ditions that lead to an empty inputname value were discussed in §21.3 If inputname(1) is not empty, lines 182–186 use the now familiar assignin command As in draw, line 182 uses

assignin to assign the modified object in the caller’s workspace Different from draw are the additional commands found in lines 183–186 These additional lines indirectly forward

do_assignin to every caller except struct.m Line 183 uses evalin to get the name of the calling module Line 184 uses strmatch to check for the string ‘struct’, and line 185 performs the indirect assignment of do_assignin

When a child class forwards get to a parent, the object is sliced and only the parent part is passed When a pass-by-reference operation is required, the parent’s get uses Code Listing 132

to assign both the mutated parent and do_assignin The child must detect a change to its own

do_assignin variable and reattach the mutated parent The parent forward block is shown in Code Listing 133; only lines 151–154 are new

Code Listing 132, Pass-by-Reference Code Block in get.m

175 if do_assignin == true

176 var_name = inputname(1);

177 if isempty(var_name)

178 warning('OOP:invalidInputname',

179 ['No assignment: pass-by-reference can only be used '

182 assignin('caller', var_name, this);

183 caller = evalin('caller', 'mfilename');

184 if isempty(strmatch(caller, {'struct'}))

185 assignin('caller', 'do_assignin', true);

188 end

Code Listing 133, Pass-by-Reference Parent Forward Assignment Commands

116 % parent forwarding block

117 if ~found

118

119 if called_by_name

120 forward_index = index(1).subs;

122 forward_index = index;

Trang 7

Line 117 guards entry into the parent forward block so that line 151 is skipped if the variable has already been found If the execution reaches line 151 and do_assignin is true, it means the parent forward operation returned a mutated parent Lines 152–153 assign the parent slice back into the child The true value that remains in do_assignin allows the commands in Code Listing 132 to complete the task of indirectly assigning the mutated object into the caller’s workspace The complete process can be difficult to follow and thus difficult to debug and maintain Class Wizard takes care of the heavy lifting All you need to do is return the correct value of

do_assignin from the helper

124

125 if nargout == 0

126 varargout = cell(size(this));

128 varargout = cell(1, nargout);

130

131 for parent_name = parent_list' % loop over parent cellstr

133 parent = [this.(parent_name{1})];

134 [varargout{:}] = get(parent, forward_index,

varargin{:});

135 found = true; % catch will assign false if not found

136 do_sub_indexing = false; % assume parent did all

sub-indexing

137 found = true; % catch will assign false if not found

138 break; % can only get here if field was found

150

151 if do_assignin

152 parent = num2cell(parent);

153 [this.(parent_name{1})] = deal(parent{:});

155

156 end

Trang 8

21.4.1.3 Pass-by-Reference Code in subsref.m

Pass-by-reference additions in subsref follow a similar pattern The commands listed for get

in Code Listing 132 are also included in subsref These commands can be found in

chapter_21/@cShape/subsref.m on lines 63–75 These commands give subsref the ability to assign the object in the caller’s workspace These commands take care of client workspace assignments, but we aren’t quite finished with the array-reference case

We need to add some commands that will ensure that an indirect assignment into

this_subset will be correctly copied back into this To do this, we need to check the value

of do_assignin and take action when the value is true The modified array-reference case is shown in Code Listing 134

Lines 48–51 are the additional commands that support pass-by-reference emulation Like normal, line 47 forwards this_subset along with all remaining index values to subsref When the execution returns to line 48, the value of do_assignin is checked If the value of do_assignin

is true, it means that the values now stored in this_subset were indirectly assigned into

subsref’s workspace Line 50 copies the subset array back into its original indices This captures the change and allows the subsequent commands in lines 63–75 to assign the mutated object into the client’s workspace

21.4.2 O THER G ROUP - OF -E IGHT C ONSIDERATIONS

Now that get and subsref have been modified to support pass-by-reference emulation, we are

in a good position to consider the potential impact on the remaining group-of-eight functions There

is no impact on the mutators set and subsasgn because they already assign this There is also no impact on the constructor because it isn’t involved in pass-by-reference emulation That leaves display, struct, and fieldnames

Display and struct both rely on the cellstr value returned by fieldnames and on the operation of get We already know that get is involved in pass-by-reference emulation, so there might be an interaction among display, struct, fieldnames, and get We explore this interaction at the end of the test drive description

Code Listing 134, Array Reference Case in subsref.m with Pass-by-Reference Commands

41 this_subset = this(index(1).subs{:});

45 % trick subsref into returning more than 1 ans

46 varargout = cell(size(this_subset));

47 [varargout{:}] = subsref(this_subset, index(2:end));

49 % the value of this_subset has also changed

50 this(index(1).subs{:}) = this_subset;

Trang 9

21.5 TEST DRIVE

There aren’t as many new items in this chapter as you might have expected Pass-by-reference support commands were discussed in this chapter; however, they have been lurking in the group-of-eight functions since Chapter 18 Thus, all preexisting functions and variables have been well tested with the pass-by-reference additions New to this chapter are the View public variable and the execution of pass-by-reference commands The test drive commands in Code Listing 135 limit their scope to test only these new elements

Code Listing 135, Chapter 21 Test Drive Command Listing: Pass-by-Reference Emulation

1 >> cd '/oop_guide/chapter_21'

2 >> set(0, 'FormatSpacing', 'compact')

3 >> clear classes; fclose all; close all force;

4 >>

5 >> star = cStar;

6 >>

7 >> get(star, 'mFigureHandle')

10 >> draw(star);

11 >> get(star, 'mFigureHandle')

12 ans =

14 >>

15 >> get(star, 'mDisplayFunc')

16 ans =

18 >> star

19 star =

23 LineWeight: 'normal'

26 >> star.View

27 ans =

29 >> get(star, 'mDisplayFunc')

30 ans =

31 developer_view

32 >>

33 >> star

34 Public Member Variables

35 star.Size = [1 1 ];

36 star.ColorRgb = [0 0 1 ];

37 star.Points = [ values omitted ];

Trang 10

Line 5 constructs a default cStar object, and line 7 displays the handle to star’s figure window Since star has not yet been drawn, its figure handle is empty (lines 8–9) Line 10 uses pass-by-reference syntax to draw the star A figure window opens and a blue star is drawn Now the figure handle has a value (lines 12–13) Pass-by-reference emulation code assigned the mutated object into the command window’s workspace even though line 10 contains no explicit assignment Next, we look at the pass-by-reference behavior added to View The initial value of star’s

mDisplayFunc is empty (Lines 16–17), and the expected display format is normal This can be seen in lines 19–25 Now things start to get interesting Line 26 accesses View and displays the value What isn’t so obvious is the fact that the access operation on line 26 also changed the object Lines 29–31 display the value of mDisplayFunc, and we see that it has changed With this value,

we expect developer view format from display That is exactly what we get in lines 34–53

We should also be able to assign star.View using mutator syntax Line 55 uses a dot-reference operator to assign false into View Internally, false is converted into an mDis-playFunc value of empty The assignment changes the display format back to normal Indeed,

38 star.LineWeight = 'normal';

39 star.View = [0];

40 star.Title = 'A Star is born';

41 Private Member Variables

42 star.mTitle = 'A Star is born';

43 star.cShape.mDisplayFunc = 'developer_view';

44 star.cShape.mSize = [1 1 ]';

45 star.cShape.mScale = [1 1 ]';

46 star.cShape.mPoints(1, :) = [ values omitted ];

47 star.cShape.mPoints(2, :) = [ values omitted ];

48 star.cShape.mFigureHandle = [];

49 star.cShape.mLineStyle.mDisplayFunc = [];

50 star.cShape.mLineStyle.mTempStatic = [];

51 star.cShape.mLineStyle.mColorHsv = [0.666666666666667 1 1 ]';

52 star.cShape.mLineStyle.mLineWidth = [1];

53 star.cShape.mLineStyle.mLineHandle = [];

54 >>

55 >> star.View = false;

56 >> star

57 star =

61 LineWeight: 'normal'

64 >>

65 >> get(star(1), 'View')

66 Warning: No assignment: pass-by-ref can't be used on indexed objects

67 > In cStar.get at 124

68 ans =

Ngày đăng: 05/08/2014, 21:21

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w